Merge pull request #173 from Fate-JH/char-fix

Character Select Screen Fix
This commit is contained in:
Fate-JH 2017-10-18 18:43:39 -04:00 committed by GitHub
commit b7fa5fa65c
11 changed files with 916 additions and 79 deletions

View file

@ -13,8 +13,8 @@ import scala.collection.mutable
class Player(private val name : String, class Player(private val name : String,
private val faction : PlanetSideEmpire.Value, private val faction : PlanetSideEmpire.Value,
private val sex : CharacterGender.Value, private val sex : CharacterGender.Value,
private val voice : Int, private val head : Int,
private val head : Int private val voice : Int
) extends PlanetSideGameObject { ) extends PlanetSideGameObject {
private var alive : Boolean = false private var alive : Boolean = false
private var backpack : Boolean = false private var backpack : Boolean = false
@ -521,11 +521,11 @@ object Player {
final val FreeHandSlot : Int = 250 final val FreeHandSlot : Int = 250
final val HandsDownSlot : Int = 255 final val HandsDownSlot : Int = 255
def apply(name : String, faction : PlanetSideEmpire.Value, sex : CharacterGender.Value, voice : Int, head : Int) : Player = { def apply(name : String, faction : PlanetSideEmpire.Value, sex : CharacterGender.Value, head : Int, voice : Int) : Player = {
new Player(name, faction, sex, voice, head) new Player(name, faction, sex, head, voice)
} }
def apply(guid : PlanetSideGUID, name : String, faction : PlanetSideEmpire.Value, sex : CharacterGender.Value, voice : Int, head : Int) : Player = { def apply(guid : PlanetSideGUID, name : String, faction : PlanetSideEmpire.Value, sex : CharacterGender.Value, head : Int, voice : Int) : Player = {
val obj = new Player(name, faction, sex, voice, head) val obj = new Player(name, faction, sex, voice, head)
obj.GUID = guid obj.GUID = guid
obj obj

View file

@ -3,7 +3,7 @@ package net.psforever.objects.definition.converter
import net.psforever.objects.{EquipmentSlot, GlobalDefinitions, ImplantSlot, Player} import net.psforever.objects.{EquipmentSlot, GlobalDefinitions, ImplantSlot, Player}
import net.psforever.objects.equipment.Equipment import net.psforever.objects.equipment.Equipment
import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, DetailedCharacterData, DrawnSlot, ImplantEffects, ImplantEntry, InternalSlot, InventoryData, PlacementData, RibbonBars, UniformStyle} import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, Cosmetics, DetailedCharacterData, DrawnSlot, ImplantEffects, ImplantEntry, InternalSlot, InventoryData, PlacementData, RibbonBars, UniformStyle}
import net.psforever.types.{GrenadeState, ImplantType} import net.psforever.types.{GrenadeState, ImplantType}
import scala.annotation.tailrec import scala.annotation.tailrec
@ -42,6 +42,7 @@ class AvatarConverter extends ObjectCreateConverter[Player]() {
MakeImplantEntries(obj), MakeImplantEntries(obj),
List.empty[String], //TODO fte list List.empty[String], //TODO fte list
List.empty[String], //TODO tutorial list List.empty[String], //TODO tutorial list
MakeCosmetics(obj.BEP),
InventoryData((MakeHolsters(obj, BuildDetailedEquipment) ++ MakeFifthSlot(obj) ++ MakeInventory(obj)).sortBy(_.parentSlot)), InventoryData((MakeHolsters(obj, BuildDetailedEquipment) ++ MakeFifthSlot(obj) ++ MakeInventory(obj)).sortBy(_.parentSlot)),
GetDrawnSlot(obj) GetDrawnSlot(obj)
) )
@ -56,7 +57,7 @@ class AvatarConverter extends ObjectCreateConverter[Player]() {
private def MakeAppearanceData(obj : Player) : CharacterAppearanceData = { private def MakeAppearanceData(obj : Player) : CharacterAppearanceData = {
CharacterAppearanceData( CharacterAppearanceData(
PlacementData(obj.Position, obj.Orientation, obj.Velocity), PlacementData(obj.Position, obj.Orientation, obj.Velocity),
BasicCharacterData(obj.Name, obj.Faction, obj.Sex, obj.Voice, obj.Head), BasicCharacterData(obj.Name, obj.Faction, obj.Sex, obj.Head, obj.Voice),
0, 0,
false, false,
false, false,
@ -132,7 +133,10 @@ class AvatarConverter extends ObjectCreateConverter[Player]() {
* @see `ImplantEntry` in `DetailedCharacterData` * @see `ImplantEntry` in `DetailedCharacterData`
*/ */
private def MakeImplantEntries(obj : Player) : List[ImplantEntry] = { private def MakeImplantEntries(obj : Player) : List[ImplantEntry] = {
obj.Implants.map(slot => { val numImplants : Int = DetailedCharacterData.numberOfImplantSlots(obj.BEP)
val implants = obj.Implants
(0 until numImplants).map(index => {
val slot = implants(index)
slot.Installed match { slot.Installed match {
case Some(_) => case Some(_) =>
if(slot.Initialized) { if(slot.Initialized) {
@ -176,6 +180,20 @@ class AvatarConverter extends ObjectCreateConverter[Player]() {
} }
} }
/**
* Should this player be of battle rank 24 or higher, they will have a mandatory cosmetics object.
* @param bep battle experience points
* @see `Cosmetics`
* @return the `Cosmetics` options
*/
protected def MakeCosmetics(bep : Long) : Option[Cosmetics] =
if(DetailedCharacterData.isBR24(bep)) {
Some(Cosmetics(false, false, false, false, false))
}
else {
None
}
/** /**
* Given a player with an inventory, convert the contents of that inventory into converted-decoded packet data. * Given a player with an inventory, convert the contents of that inventory into converted-decoded packet data.
* The inventory is not represented in a `0x17` `Player`, so the conversion is only valid for `0x18` avatars. * The inventory is not represented in a `0x17` `Player`, so the conversion is only valid for `0x18` avatars.
@ -236,7 +254,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
*/ */
private def BuildDetailedEquipment(index : Int, equip : Equipment) : InternalSlot = { protected 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)
} }
@ -274,7 +292,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
*/ */
private def GetDrawnSlot(obj : Player) : DrawnSlot.Value = { protected 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

@ -0,0 +1,102 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.definition.converter
import net.psforever.objects.{EquipmentSlot, Player}
import net.psforever.objects.equipment.Equipment
import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, DetailedCharacterData, ImplantEntry, InternalSlot, InventoryData, PlacementData, RibbonBars}
import net.psforever.types.{GrenadeState, ImplantType}
import scala.annotation.tailrec
import scala.util.{Failure, Success, Try}
/**
* `CharacterSelectConverter` is a simplified `AvatarConverter`
* that is tailored for appearance of the player character on the character selection screen.
* Details that would not be apparent on that screen such as implants or certifications are ignored.
*/
class CharacterSelectConverter extends AvatarConverter {
override def ConstructorData(obj : Player) : Try[CharacterData] = Failure(new Exception("CharacterSelectConverter should not be used to generate CharacterData"))
override def DetailedConstructorData(obj : Player) : Try[DetailedCharacterData] = {
Success(
DetailedCharacterData(
MakeAppearanceData(obj),
obj.BEP,
obj.CEP,
1, 1, 0, 1, 1,
Nil,
MakeImplantEntries(obj), //necessary for correct stream length
Nil, Nil,
MakeCosmetics(obj.BEP),
InventoryData(recursiveMakeHolsters(obj.Holsters().iterator)),
GetDrawnSlot(obj)
)
)
}
/**
* Compose some data from a `Player` into a representation common to both `CharacterData` and `DetailedCharacterData`.
* @param obj the `Player` game object
* @see `AvatarConverter.MakeAppearanceData`
* @return the resulting `CharacterAppearanceData`
*/
private def MakeAppearanceData(obj : Player) : CharacterAppearanceData = {
CharacterAppearanceData(
PlacementData(0f, 0f, 0f),
BasicCharacterData(obj.Name, obj.Faction, obj.Sex, obj.Head, 1),
0,
false,
false,
obj.ExoSuit,
"",
0,
false,
0f,
0f,
true,
GrenadeState.None,
false,
false,
false,
RibbonBars()
)
}
/**
* Transform an `Array` of `Implant` objects into a `List` of `ImplantEntry` objects suitable as packet data.
* @param obj the `Player` game object
* @return the resulting implant `List`
* @see `ImplantEntry` in `DetailedCharacterData`
*/
private def MakeImplantEntries(obj : Player) : List[ImplantEntry] = {
List.fill[ImplantEntry](DetailedCharacterData.numberOfImplantSlots(obj.BEP))(ImplantEntry(ImplantType.None, None))
}
/**
* Given some equipment holsters, convert the contents of those holsters into converted-decoded packet data.
* @param iter an `Iterator` of `EquipmentSlot` objects that are a part of the player's holsters
* @param list the current `List` of transformed data
* @param index which holster is currently being explored
* @see `AvatarConverter.recursiveMakeHolsters`
* @return the `List` of inventory data created from the holsters
*/
@tailrec private def recursiveMakeHolsters(iter : Iterator[EquipmentSlot], list : List[InternalSlot] = Nil, index : Int = 0) : List[InternalSlot] = {
if(!iter.hasNext) {
list
}
else {
val slot : EquipmentSlot = iter.next
if(slot.Equipment.isDefined) {
val equip : Equipment = slot.Equipment.get
recursiveMakeHolsters(
iter,
list :+ BuildDetailedEquipment(index, equip),
index + 1
)
}
else {
recursiveMakeHolsters(iter, list, index + 1)
}
}
}
}

View file

@ -41,24 +41,6 @@ object UniformStyle extends Enumeration {
implicit val codec = PacketHelpers.createEnumerationCodec(this, uintL(3)) implicit val codec = PacketHelpers.createEnumerationCodec(this, uintL(3))
} }
/**
* The different cosmetics that a player can apply to their model's head.<br>
* <br>
* The player gets the ability to apply these minor modifications at battle rank twenty-four, just one rank before the third uniform upgrade.
* @param no_helmet removes the current helmet on the reinforced exo-suit and the agile exo-suit;
* all other cosmetics require `no_helmet` to be `true` before they can be seen
* @param beret player dons a beret
* @param sunglasses player dons sunglasses
* @param earpiece player dons an earpiece on the left
* @param brimmed_cap player dons a cap;
* the cap overrides the beret, if both are selected
*/
final case class Cosmetics(no_helmet : Boolean,
beret : Boolean,
sunglasses : Boolean,
earpiece : Boolean,
brimmed_cap : Boolean)
/** /**
* A part of a representation of the avatar portion of `ObjectCreateMessage` packet data. * A part of a representation of the avatar portion of `ObjectCreateMessage` packet data.
* This densely-packed information outlines most of the specifics of depicting some other character.<br> * This densely-packed information outlines most of the specifics of depicting some other character.<br>
@ -118,7 +100,7 @@ final case class CharacterData(appearance : CharacterAppearanceData,
//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 appearanceSize : Long = appearance.bitsize
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) { 5L } else { 0L } val cosmeticsSize : Long = if(cosmetics.isDefined) { cosmetics.get.bitsize } else { 0L }
val inventorySize : Long = if(inventory.isDefined) { inventory.get.bitsize } else { 0L } val inventorySize : Long = if(inventory.isDefined) { inventory.get.bitsize } else { 0L }
32L + appearanceSize + effectsSize + cosmeticsSize + inventorySize 32L + appearanceSize + effectsSize + cosmeticsSize + inventorySize
} }
@ -141,19 +123,6 @@ object CharacterData extends Marshallable[CharacterData] {
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(appearance : CharacterAppearanceData, health : Int, armor : Int, uniform : UniformStyle.Value, cr : Int, implant_effects : Option[ImplantEffects.Value], cosmetics : Option[Cosmetics], inv : InventoryData, drawn_slot : DrawnSlot.Value) : CharacterData =
new CharacterData(appearance, health, armor, uniform, cr, implant_effects, cosmetics, Some(inv), drawn_slot) new CharacterData(appearance, health, armor, uniform, cr, implant_effects, cosmetics, Some(inv), drawn_slot)
/**
* Check for the bit flags for the cosmetic items.
* These flags are only valid if the player has acquired their third uniform upgrade.
* @see `UniformStyle.ThirdUpgrade`
*/
private val cosmeticsCodec : Codec[Cosmetics] = (
("no_helmet" | bool) ::
("beret" | bool) ::
("sunglasses" | bool) ::
("earpiece" | bool) ::
("brimmed_cap" | bool)
).as[Cosmetics]
implicit val codec : Codec[CharacterData] = ( implicit val codec : Codec[CharacterData] = (
("app" | CharacterAppearanceData.codec) :: ("app" | CharacterAppearanceData.codec) ::
("health" | uint8L) :: //dead state when health == 0 ("health" | uint8L) :: //dead state when health == 0
@ -163,7 +132,7 @@ object CharacterData extends Marshallable[CharacterData] {
("command_rank" | uintL(3)) :: ("command_rank" | uintL(3)) ::
bool :: //stream misalignment when != 1 bool :: //stream misalignment when != 1
optional(bool, "implant_effects" | ImplantEffects.codec) :: optional(bool, "implant_effects" | ImplantEffects.codec) ::
conditional(style == UniformStyle.ThirdUpgrade, "cosmetics" | cosmeticsCodec) :: conditional(style == UniformStyle.ThirdUpgrade, "cosmetics" | Cosmetics.codec) ::
optional(bool, "inventory" | InventoryData.codec) :: optional(bool, "inventory" | InventoryData.codec) ::
("drawn_slot" | DrawnSlot.codec) :: ("drawn_slot" | DrawnSlot.codec) ::
bool //usually false bool //usually false

View file

@ -0,0 +1,41 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
import scodec.codecs._
import scodec.Codec
/**
* The different cosmetics that a player can apply to their character model's head.<br>
* <br>
* The player gets the ability to apply these minor modifications at battle rank twenty-four, just one rank before the third uniform upgrade.
* These flags are only valid if the player has:
* for `DetailedCharacterData`, achieved at least battle rank twenty-four (battle experience points greater than 2286230),
* or, for `CharacterData`, achieved at least battle rank twenty-five (acquired their third uniform upgrade).
* `CharacterData`, as implied, will not display these options until one battle rank after they would have become available.
* @param no_helmet removes the current helmet on the reinforced exo-suit and the agile exo-suit;
* all other cosmetics require `no_helmet` to be `true` before they can be seen
* @param beret player dons a beret
* @param sunglasses player dons sunglasses
* @param earpiece player dons an earpiece on the left
* @param brimmed_cap player dons a cap;
* the cap overrides the beret, if both are selected
* @see `UniformStyle.ThirdUpgrade`
*/
final case class Cosmetics(no_helmet : Boolean,
beret : Boolean,
sunglasses : Boolean,
earpiece : Boolean,
brimmed_cap : Boolean
) extends StreamBitSize {
override def bitsize : Long = 5L
}
object Cosmetics {
implicit val codec : Codec[Cosmetics] = (
("no_helmet" | bool) ::
("beret" | bool) ::
("sunglasses" | bool) ::
("earpiece" | bool) ::
("brimmed_cap" | bool)
).as[Cosmetics]
}

View file

@ -70,6 +70,9 @@ final case class ImplantEntry(implant : ImplantType.Value,
* @param tutorials the `List` of tutorials completed by this avatar; * @param tutorials the `List` of tutorials completed by this avatar;
* the size field is a 32-bit number; * the size field is a 32-bit number;
* the first entry may be padded * the first entry may be padded
* @param cosmetics optional decorative features that are added to the player's head model by console/chat commands;
* they become available at battle rank 24;
* these flags do not exist if they are not applicable
* @param inventory the avatar's inventory * @param inventory the avatar's inventory
* @param drawn_slot the holster that is initially drawn * @param drawn_slot the holster that is initially drawn
* @see `CharacterAppearanceData`<br> * @see `CharacterAppearanceData`<br>
@ -93,6 +96,7 @@ 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],
inventory : Option[InventoryData], inventory : Option[InventoryData],
drawn_slot : DrawnSlot.Value = DrawnSlot.None drawn_slot : DrawnSlot.Value = DrawnSlot.None
) extends ConstructorData { ) extends ConstructorData {
@ -116,13 +120,16 @@ final case class DetailedCharacterData(appearance : CharacterAppearanceData,
for(str <- tutorials) { for(str <- tutorials) {
tutorialListSize += StreamBitSize.stringBitSize(str) tutorialListSize += StreamBitSize.stringBitSize(str)
} }
val br24 = DetailedCharacterData.isBR24(bep) //character is at least BR24
val extraBitSize : Long = if(br24) { 33L } else { 46L }
val cosmeticsSize : Long = if(br24) { cosmetics.get.bitsize } else { 0L }
val inventorySize : Long = if(inventory.isDefined) { //inventory val inventorySize : Long = if(inventory.isDefined) { //inventory
inventory.get.bitsize inventory.get.bitsize
} }
else { else {
0L 0L
} }
649L + appearanceSize + certSize + implantSize + eventListSize + tutorialListSize + inventorySize 603L + appearanceSize + certSize + implantSize + eventListSize + extraBitSize + cosmeticsSize + tutorialListSize + inventorySize
} }
} }
@ -145,8 +152,8 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
* @param drawn_slot the holster that is initially drawn * @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], inventory : InventoryData, drawn_slot : DrawnSlot.Value) : DetailedCharacterData = def apply(appearance : CharacterAppearanceData, bep : Long, cep : Long, healthMax : Int, health : Int, armor : Int, staminaMax : Int, stamina : Int, certs : List[CertificationType.Value], implants : List[ImplantEntry], firstTimeEvents : List[String], tutorials : List[String], cosmetics : Option[Cosmetics], inventory : InventoryData, drawn_slot : DrawnSlot.Value) : DetailedCharacterData =
new DetailedCharacterData(appearance, bep, cep, healthMax, health, armor, 1, 7, 7, staminaMax, stamina, certs, implants, firstTimeEvents, tutorials, Some(inventory), drawn_slot) new DetailedCharacterData(appearance, bep, cep, healthMax, health, armor, 1, 7, 7, staminaMax, stamina, certs, implants, firstTimeEvents, tutorials, cosmetics, Some(inventory), drawn_slot)
/** /**
* `Codec` for entries in the `List` of implants. * `Codec` for entries in the `List` of implants.
@ -179,7 +186,7 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
* @param bep battle experience points * @param bep battle experience points
* @return the number of accessible implant slots * @return the number of accessible implant slots
*/ */
private def numberOfImplantSlots(bep : Long) : Int = { def numberOfImplantSlots(bep : Long) : Int = {
if(bep > 754370) { //BR18+ if(bep > 754370) { //BR18+
3 3
} }
@ -209,7 +216,7 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
implantOffset += entry.bitsize.toInt implantOffset += entry.bitsize.toInt
}) })
val resultB : Int = resultA - (implantOffset % 8) val resultB : Int = resultA - (implantOffset % 8)
if(resultB < 0) { 8 - resultB } else { resultB } if(resultB < 0) { 8 + resultB } else { resultB }
} }
/** /**
@ -269,6 +276,8 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
} }
} }
def isBR24(bep : Long) : Boolean = bep > 2286230
implicit val codec : Codec[DetailedCharacterData] = ( implicit val codec : Codec[DetailedCharacterData] = (
("appearance" | CharacterAppearanceData.codec) >>:~ { app => ("appearance" | CharacterAppearanceData.codec) >>:~ { app =>
("bep" | uint32L) >>:~ { bep => ("bep" | uint32L) >>:~ { bep =>
@ -297,10 +306,14 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
(("tutorial_length" | uint32L) >>:~ { len2 => (("tutorial_length" | uint32L) >>:~ { len2 =>
conditional(len2 > 0, "tutorial_firstEntry" | PacketHelpers.encodedStringAligned(tutPadding(len, len2, implantFieldPadding(implants, CharacterAppearanceData.altModelBit(app))))) :: 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(207) :: ignore(160) ::
optional(bool, "inventory" | InventoryData.codec_detailed) :: (bool >>:~ { br24 => //BR24+
("drawn_slot" | DrawnSlot.codec) :: newcodecs.binary_choice(br24, ignore(33), ignore(46)) ::
bool //usually false conditional(br24, Cosmetics.codec) ::
optional(bool, "inventory" | InventoryData.codec_detailed) ::
("drawn_slot" | DrawnSlot.codec) ::
bool //usually false
})
}) })
}) })
}) })
@ -308,14 +321,14 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
} }
).exmap[DetailedCharacterData] ( ).exmap[DetailedCharacterData] (
{ {
case app :: bep :: cep :: _ :: hpmax :: hp :: _ :: armor :: _ :: u1 :: _ :: u2 :: u3 :: stamax :: stam :: _ :: certs :: _ :: _ :: implants :: _ :: _ :: fte0 :: fte1 :: _ :: tut0 :: tut1 :: _ :: inv :: drawn :: false :: HNil => case app :: bep :: cep :: _ :: hpmax :: hp :: _ :: armor :: _ :: u1 :: _ :: u2 :: u3 :: stamax :: stam :: _ :: certs :: _ :: _ :: implants :: _ :: _ :: fte0 :: fte1 :: _ :: tut0 :: tut1 :: _ :: _ :: _ :: cosmetics :: inv :: drawn :: false :: 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, inv, drawn)) Attempt.successful(DetailedCharacterData(app, bep, cep, hpmax, hp, armor, u1, u2, u3, stamax, stam, certs, implants, fteList, tutList, cosmetics, inv, drawn))
}, },
{ {
case DetailedCharacterData(app, bep, cep, hpmax, hp, armor, u1, u2, u3, stamax, stam, certs, implants, fteList, tutList, inv, drawn) => case DetailedCharacterData(app, bep, cep, hpmax, hp, armor, u1, u2, u3, stamax, stam, certs, implants, fteList, tutList, cos, inv, drawn) =>
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)
@ -334,7 +347,9 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
case ((f : String) +: (rest : List[String])) => (Some(f), rest) case ((f : String) +: (rest : List[String])) => (Some(f), rest)
case Nil => (None, Nil) case Nil => (None, Nil)
} }
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 :: () :: inv :: drawn :: false :: HNil) val br24 : Boolean = isBR24(bep)
val cosmetics : Option[Cosmetics] = if(br24) { cos } else { None }
Attempt.successful(app :: bep :: cep :: () :: hpmax :: hp :: () :: armor :: () :: u1 :: () :: u2 :: u3 :: stamax :: stam :: () :: certs :: None :: () :: implantList :: () :: fteList.size.toLong :: firstEvent :: fteListCopy :: tutList.size.toLong :: firstTutorial :: tutListCopy :: () :: br24 :: () :: cosmetics :: inv :: drawn :: false :: HNil)
} }
) )
} }

View file

@ -11,9 +11,12 @@ import shapeless.{::, HNil}
* This data will help construct the "tool" called a Remote Electronics Kit.<br> * This data will help construct the "tool" called a Remote Electronics Kit.<br>
* <br> * <br>
* Of note is the first portion of the data which resembles the `DetailedWeaponData` format. * Of note is the first portion of the data which resembles the `DetailedWeaponData` format.
* @param unk na * @param unk1 na
* @param unk2 na
*/ */
final case class DetailedREKData(unk : Int) extends ConstructorData { final case class DetailedREKData(unk1 : Int,
unk2 : Int = 0
) extends ConstructorData {
override def bitsize : Long = 67L override def bitsize : Long = 67L
} }
@ -25,17 +28,17 @@ object DetailedREKData extends Marshallable[DetailedREKData] {
uint4L :: uint4L ::
uint16L :: uint16L ::
uint4L :: uint4L ::
uintL(15) ("unk2" | uintL(15))
).exmap[DetailedREKData] ( ).exmap[DetailedREKData] (
{ {
case code :: 8 :: 0 :: 2 :: 0 :: 8 :: 0 :: HNil => case code :: 8 :: 0 :: 2 :: 0 :: 8 :: unk2 :: HNil =>
Attempt.successful(DetailedREKData(code)) Attempt.successful(DetailedREKData(code, unk2))
case _ => case _ =>
Attempt.failure(Err("invalid rek data format")) Attempt.failure(Err("invalid rek data format"))
}, },
{ {
case DetailedREKData(code) => case DetailedREKData(code, unk2) =>
Attempt.successful(code :: 8 :: 0 :: 2 :: 0 :: 8 :: 0 :: HNil) Attempt.successful(code :: 8 :: 0 :: 2 :: 0 :: 8 :: unk2 :: HNil)
} }
) )
} }

View file

@ -23,7 +23,9 @@ import scodec.codecs._
*/ */
object ImplantType extends Enumeration { object ImplantType extends Enumeration {
type Type = Value type Type = Value
val AdvancedRegen,
val
AdvancedRegen,
Targeting, Targeting,
AudioAmplifier, AudioAmplifier,
DarklightVision, DarklightVision,

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package objects package objects
import net.psforever.objects.definition.converter.{ACEConverter, REKConverter} import net.psforever.objects.definition.converter.{ACEConverter, CharacterSelectConverter, REKConverter}
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.objects.definition._ import net.psforever.objects.definition._
import net.psforever.objects.equipment.CItem.{DeployedItem, Unit} import net.psforever.objects.equipment.CItem.{DeployedItem, Unit}
@ -130,7 +130,7 @@ class ConverterTest extends Specification {
} }
"Player" should { "Player" should {
"convert to packet" in { val obj : Player = {
/* /*
Create an AmmoBoxDefinition with which to build two AmmoBoxes Create an AmmoBoxDefinition with which to build two AmmoBoxes
Create a ToolDefinition with which to create a Tool Create a ToolDefinition with which to create a Tool
@ -157,9 +157,64 @@ class ConverterTest extends Specification {
obj.Slot(2).Equipment = tool obj.Slot(2).Equipment = tool
obj.Slot(5).Equipment.get.GUID = PlanetSideGUID(94) obj.Slot(5).Equipment.get.GUID = PlanetSideGUID(94)
obj.Inventory += 8 -> box2 obj.Inventory += 8 -> box2
obj
}
val converter = new CharacterSelectConverter
obj.Definition.Packet.DetailedConstructorData(obj).isSuccess mustEqual true "convert to packet (BR < 24)" in {
ok //TODO write more of this test obj.BEP = 0
obj.Definition.Packet.DetailedConstructorData(obj) match {
case Success(pkt) =>
ok
case _ =>
ko
}
obj.Definition.Packet.ConstructorData(obj) match {
case Success(pkt) =>
ok
case _ =>
ko
}
}
"convert to packet (BR >= 24)" in {
obj.BEP = 10000000
obj.Definition.Packet.DetailedConstructorData(obj) match {
case Success(pkt) =>
ok
case _ =>
ko
}
obj.Definition.Packet.ConstructorData(obj) match {
case Success(pkt) =>
ok
case _ =>
ko
}
}
"convert to simple packet (BR < 24)" in {
obj.BEP = 0
converter.DetailedConstructorData(obj) match {
case Success(pkt) =>
ok
case _ =>
ko
}
converter.ConstructorData(obj).isFailure mustEqual true
converter.ConstructorData(obj).get must throwA[Exception]
}
"convert to simple packet (BR >= 24)" in {
obj.BEP = 10000000
converter.DetailedConstructorData(obj) match {
case Success(pkt) =>
ok
case _ =>
ko
}
converter.ConstructorData(obj).isFailure mustEqual true
converter.ConstructorData(obj).get must throwA[Exception]
} }
} }

View file

@ -415,7 +415,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
} }
case ListAccountCharacters => case ListAccountCharacters =>
import net.psforever.objects.definition.converter.CharacterSelectConverter
val gen : AtomicInteger = new AtomicInteger(1) val gen : AtomicInteger = new AtomicInteger(1)
val converter : CharacterSelectConverter = new CharacterSelectConverter
//load characters //load characters
SetCharacterSelectScreenGUID(player, gen) SetCharacterSelectScreenGUID(player, gen)
@ -424,7 +426,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
val armor = player.Armor val armor = player.Armor
player.Spawn player.Spawn
sendResponse(PacketCoding.CreateGamePacket(0, sendResponse(PacketCoding.CreateGamePacket(0,
ObjectCreateMessage(ObjectClass.avatar, player.GUID, player.Definition.Packet.ConstructorData(player).get) ObjectCreateDetailedMessage(ObjectClass.avatar, player.GUID, converter.DetailedConstructorData(player).get)
)) ))
if(health > 0) { //player can not be dead; stay spawned as alive if(health > 0) { //player can not be dead; stay spawned as alive
player.Health = health player.Health = health
@ -652,6 +654,11 @@ class WorldSessionActor extends Actor with MDCContextAware {
log.info(s"New world login to $server with Token:$token. $clientVersion") log.info(s"New world login to $server with Token:$token. $clientVersion")
self ! ListAccountCharacters self ! ListAccountCharacters
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
clientKeepAlive.cancel
clientKeepAlive = context.system.scheduler.schedule(0 seconds, 500 milliseconds, self, PokeClient())
case msg @ CharacterCreateRequestMessage(name, head, voice, gender, empire) => case msg @ CharacterCreateRequestMessage(name, head, voice, gender, empire) =>
log.info("Handling " + msg) log.info("Handling " + msg)
sendResponse(PacketCoding.CreateGamePacket(0, ActionResultMessage(true, None))) sendResponse(PacketCoding.CreateGamePacket(0, ActionResultMessage(true, None)))
@ -668,11 +675,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
//TODO if yes, get continent guid accessors //TODO if yes, get continent guid accessors
//TODO if no, get sanctuary guid accessors and reset the player's expectations //TODO if no, get sanctuary guid accessors and reset the player's expectations
galaxy ! InterstellarCluster.GetWorld("home3") galaxy ! InterstellarCluster.GetWorld("home3")
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
clientKeepAlive.cancel
clientKeepAlive = context.system.scheduler.schedule(0 seconds, 500 milliseconds, self, PokeClient())
case default => case default =>
log.error("Unsupported " + default + " in " + msg) log.error("Unsupported " + default + " in " + msg)
} }
@ -699,7 +701,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
) )
}) })
//render Equipment that was dropped into zone before the player arrived //render Equipment that was dropped into zone before the player arrived
continent.EquipmentOnGround.toList.foreach(item => { continent.EquipmentOnGround.foreach(item => {
val definition = item.Definition val definition = item.Definition
sendResponse( sendResponse(
PacketCoding.CreateGamePacket(0, PacketCoding.CreateGamePacket(0,