Merge pull request #147 from Fate-JH/displayed-award

Packet: DisplayedAwardMessage
This commit is contained in:
Fate-JH 2017-05-10 08:32:19 -04:00 committed by GitHub
commit 7d3b10da48
8 changed files with 658 additions and 35 deletions

View file

@ -567,7 +567,7 @@ object GamePacketOpcode extends Enumeration {
// OPCODES 0xd0-df // OPCODES 0xd0-df
case 0xd0 => noDecoder(UnknownMessage208) case 0xd0 => noDecoder(UnknownMessage208)
case 0xd1 => noDecoder(DisplayedAwardMessage) case 0xd1 => game.DisplayedAwardMessage.decode
case 0xd2 => noDecoder(RespawnAMSInfoMessage) case 0xd2 => noDecoder(RespawnAMSInfoMessage)
case 0xd3 => noDecoder(ComponentDamageMessage) case 0xd3 => noDecoder(ComponentDamageMessage)
case 0xd4 => noDecoder(GenericObjectActionAtPositionMessage) case 0xd4 => noDecoder(GenericObjectActionAtPositionMessage)

View file

@ -0,0 +1,60 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
import net.psforever.types.MeritCommendation
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import scodec.Codec
import scodec.codecs._
/**
* An `Enumeration` of the slots for award ribbons on a player's `RibbonBars`.
*/
object RibbonBarsSlot extends Enumeration {
type Type = Value
val Top,
Middle,
Bottom,
TermOfService //technically,the slot above "Top"
= Value
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint4L)
}
/**
* Dispatched to configure a player's merit commendation ribbons.<br>
* <br>
* Normally, this packet is dispatched by the client when managing merit commendations through the "Character Info/Achievements" tab.
* On Gemini Live, this packet was also always dispatched once by the server during character login.
* It set the term of service ribbon explicitly.
* Generally, this was unnecessary, as the encoded character data maintains information about displayed ribbons.
* This behavior was probably a routine that ensured that correct yearly progression was tracked if the player earned it while offline.
* It never set any of the other ribbon slot positions during login.<br>
* <br>
* A specific ribbon may only be set once to one slot.
* The last set slot is considered the valid position to which that ribbon will be placed/moved.
* @param player_guid the player
* @param ribbon the award to be displayed;
* defaults to `MeritCommendation.None`;
* use `MeritCommendation.None` when indicating "no ribbon"
* @param bar any of the four positions where the award ribbon is to be displayed;
* defaults to `TermOfService`
* @see `RibbonBars`
* @see `MeritCommendation`
*/
final case class DisplayedAwardMessage(player_guid : PlanetSideGUID,
ribbon : MeritCommendation.Value = MeritCommendation.None,
bar : RibbonBarsSlot.Value = RibbonBarsSlot.TermOfService)
extends PlanetSideGamePacket {
type Packet = DisplayedAwardMessage
def opcode = GamePacketOpcode.DisplayedAwardMessage
def encode = DisplayedAwardMessage.encode(this)
}
object DisplayedAwardMessage extends Marshallable[DisplayedAwardMessage] {
implicit val codec : Codec[DisplayedAwardMessage] = (
("player_guid" | PlanetSideGUID.codec) ::
("ribbon" | MeritCommendation.codec) ::
("bar" | RibbonBarsSlot.codec)
).as[DisplayedAwardMessage]
}

View file

@ -55,8 +55,10 @@ object ObjectCreateDetailedMessage extends Marshallable[ObjectCreateDetailedMess
* @param data the data used to construct this type of object * @param data the data used to construct this type of object
* @return an ObjectCreateMessage * @return an ObjectCreateMessage
*/ */
def apply(objectClass : Int, guid : PlanetSideGUID, parentInfo : ObjectCreateMessageParent, data : ConstructorData) : ObjectCreateDetailedMessage = def apply(objectClass : Int, guid : PlanetSideGUID, parentInfo : ObjectCreateMessageParent, data : ConstructorData) : ObjectCreateDetailedMessage = {
ObjectCreateDetailedMessage(0L, objectClass, guid, Some(parentInfo), Some(data)) val parentInfoOpt : Option[ObjectCreateMessageParent] = Some(parentInfo)
ObjectCreateDetailedMessage(ObjectCreateBase.streamLen(parentInfoOpt, data), objectClass, guid, parentInfoOpt, Some(data))
}
/** /**
* An abbreviated constructor for creating `ObjectCreateMessages`, ignoring `parentInfo`. * An abbreviated constructor for creating `ObjectCreateMessages`, ignoring `parentInfo`.
@ -65,8 +67,9 @@ object ObjectCreateDetailedMessage extends Marshallable[ObjectCreateDetailedMess
* @param data the data used to construct this type of object * @param data the data used to construct this type of object
* @return an ObjectCreateMessage * @return an ObjectCreateMessage
*/ */
def apply(objectClass : Int, guid : PlanetSideGUID, data : ConstructorData) : ObjectCreateDetailedMessage = def apply(objectClass : Int, guid : PlanetSideGUID, data : ConstructorData) : ObjectCreateDetailedMessage = {
ObjectCreateDetailedMessage(0L, 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. * Take the important information of a game piece and transform it into bit data.

View file

@ -2,35 +2,33 @@
package net.psforever.packet.game.objectcreate package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable import net.psforever.packet.Marshallable
import net.psforever.types.MeritCommendation
import scodec.Codec import scodec.Codec
import scodec.codecs._ import scodec.codecs._
/** /**
* Enumerate the player-displayed merit commendation awards granted for excellence (or tenacity) in combat. * Enumerate the player-displayed merit commendation awards granted for excellence (or tenacity) in combat.
* These are the medals players wish to brandish on their left pauldron.<br> * These are the medals players wish to brandish on their left pauldron.
* <br>
* All merit commendation ribbons are represented by a 32-bit signature.
* The default "no-ribbon" value is `0xFFFFFFFF`, although some illegal values will also work.
* The term of service ribbon can not be modified by the user and will apply itself to its slot automatically when valid.
* @param upper the "top" configurable merit ribbon * @param upper the "top" configurable merit ribbon
* @param middle the central configurable merit ribbon * @param middle the central configurable merit ribbon
* @param lower the lower configurable merit ribbon * @param lower the lower configurable merit ribbon
* @param tos the top-most term of service merit ribbon * @param tos the top-most term of service merit ribbon
* @see `MeritCommendation`
* @see `DisplayedAwardMessage`
*/ */
final case class RibbonBars(upper : Long = RibbonBars.noRibbon, final case class RibbonBars(upper : MeritCommendation.Value = MeritCommendation.None,
middle : Long = RibbonBars.noRibbon, middle : MeritCommendation.Value = MeritCommendation.None,
lower : Long = RibbonBars.noRibbon, lower : MeritCommendation.Value = MeritCommendation.None,
tos : Long = RibbonBars.noRibbon) extends StreamBitSize { tos : MeritCommendation.Value = MeritCommendation.None
) extends StreamBitSize {
override def bitsize : Long = 128L override def bitsize : Long = 128L
} }
object RibbonBars extends Marshallable[RibbonBars] { object RibbonBars extends Marshallable[RibbonBars] {
val noRibbon : Long = 0xFFFFFFFFL
implicit val codec : Codec[RibbonBars] = ( implicit val codec : Codec[RibbonBars] = (
("upper" | uint32L) :: ("upper" | MeritCommendation.codec) ::
("middle" | uint32L) :: ("middle" | MeritCommendation.codec) ::
("lower" | uint32L) :: ("lower" | MeritCommendation.codec) ::
("tos" | uint32L) ("tos" | MeritCommendation.codec)
).as[RibbonBars] ).as[RibbonBars]
} }

View file

@ -0,0 +1,522 @@
// Copyright (c) 2017 PSForever
package net.psforever.types
import scodec.{Attempt, Err}
import scodec.codecs._
/**
* An `Enumeration` of all merit commendation award categories organized into associated ribbons.
* By astonishing coincidence, with exception of the first ten special awards, the rest of list is in alphabetical order.
*/
object MeritCommendation extends Enumeration {
type Type = Value
val //0
FanFaire2005Commander,
FanFaire2005Soldier,
FanFaire2006Atlanta,
HalloweenMassacre2006NC,
HalloweenMassacre2006TR,
HalloweenMassacre2006VS,
FanFaire2007,
FanFaire2008,
FanFaire2009,
AdvancedMedic1,
//10
AdvancedMedic2,
AdvancedMedic3,
AdvancedMedic4,
AdvancedMedic5,
AdvancedMedic6,
AdvancedMedic7,
AdvancedMedicAssists1,
AdvancedMedicAssists2,
AdvancedMedicAssists3,
AdvancedMedicAssists4,
//20
AdvancedMedicAssists5,
AdvancedMedicAssists6,
AdvancedMedicAssists7,
AirDefender1,
AirDefender2,
AirDefender3,
AirDefender4,
AirDefender5,
AirDefender6,
AirDefender7,
//30
AMSSupport1,
AMSSupport2,
AMSSupport3,
AMSSupport4,
AMSSupport5,
AMSSupport6,
AMSSupport7,
AntiVehicular1,
AntiVehicular2,
AntiVehicular3,
//40
AntiVehicular4,
AntiVehicular5,
AntiVehicular6,
AntiVehicular7,
Avenger1,
Avenger2,
Avenger3,
Avenger4,
Avenger5,
Avenger6,
//50
Avenger7,
AwardColors, //what?
BendingMovieActor,
BFRAdvanced,
BFRAdvanced2,
BFRAdvanced3,
BFRAdvanced4,
BFRAdvanced5,
BFRBuster1,
BFRBuster2,
//60
BFRBuster3,
BFRBuster4,
BFRBuster5,
BFRBuster6,
BFRBuster7,
BlackOpsHunter1,
BlackOpsHunter2,
BlackOpsHunter3,
BlackOpsHunter4,
BlackOpsHunter5,
//70
BlackOpsParticipant,
BlackOpsVictory,
Bombadier1,
Bombadier2,
Bombadier3,
Bombadier4,
Bombadier5,
Bombadier6,
Bombadier7,
BomberAce1,
//80
BomberAce2,
BomberAce3,
BomberAce4,
BomberAce5,
BomberAce6,
BomberAce7,
Boomer1,
Boomer2,
Boomer3,
Boomer4,
//90
Boomer5,
Boomer6,
Boomer7,
CalvaryDriver1,
CalvaryDriver2,
CalvaryDriver3,
CalvaryDriver4,
CalvaryDriver5,
CalvaryDriver6,
CalvaryDriver7,
//100
CalvaryPilot,
CalvaryPilot2,
CalvaryPilot3,
CalvaryPilot4,
CalvaryPilot5,
CalvaryPilot6,
CalvaryPilot7,
CMTopOutfit,
CombatMedic,
CombatMedic2,
//110
CombatMedic3,
CombatMedic4,
CombatMedic5,
CombatMedic6,
CombatMedic7,
CombatRepair1,
CombatRepair2,
CombatRepair3,
CombatRepair4,
CombatRepair5,
//120
CombatRepair6,
CombatRepair7,
ContestFirstBR40,
ContestMovieMaker,
ContestMovieMakerOutfit,
ContestPlayerOfTheMonth,
ContestPlayerOfTheYear,
CSAppreciation,
DefenseNC1,
DefenseNC2,
//130
DefenseNC3,
DefenseNC4,
DefenseNC5,
DefenseNC6,
DefenseNC7,
DefenseTR1,
DefenseTR2,
DefenseTR3,
DefenseTR4,
DefenseTR5,
//40
DefenseTR6,
DefenseTR7,
DefenseVS1,
DefenseVS2,
DefenseVS3,
DefenseVS4,
DefenseVS5,
DefenseVS6,
DefenseVS7,
DevilDogsMovie,
//150
DogFighter1,
DogFighter2,
DogFighter3,
DogFighter4,
DogFighter5,
DogFighter6,
DogFighter7,
DriverGunner1,
DriverGunner2,
DriverGunner3,
//160
DriverGunner4,
DriverGunner5,
DriverGunner6,
DriverGunner7,
EliteAssault0,
EliteAssault1,
EliteAssault2,
EliteAssault3,
EliteAssault4,
EliteAssault5,
//170
EliteAssault6,
EliteAssault7,
EmeraldVeteran,
Engineer1,
Engineer2,
Engineer3,
Engineer4,
Engineer5,
Engineer6,
EquipmentSupport1,
//180
EquipmentSupport2,
EquipmentSupport3,
EquipmentSupport4,
EquipmentSupport5,
EquipmentSupport6,
EquipmentSupport7,
EventNCCommander,
EventNCElite,
EventNCSoldier,
EventTRCommander,
//190
EventTRElite,
EventTRSoldier,
EventVSCommander,
EventVSElite,
EventVSSoldier,
Explorer1,
FiveYearNC,
FiveYearTR,
FiveYearVS,
FourYearNC,
//200
FourYearTR,
FourYearVS,
GalaxySupport1,
GalaxySupport2,
GalaxySupport3,
GalaxySupport4,
GalaxySupport5,
GalaxySupport6,
GalaxySupport7,
Grenade1,
//210
Grenade2,
Grenade3,
Grenade4,
Grenade5,
Grenade6,
Grenade7,
GroundGunner1,
GroundGunner2,
GroundGunner3,
GroundGunner4,
//220
GroundGunner5,
GroundGunner6,
GroundGunner7,
HackingSupport1,
HackingSupport2,
HackingSupport3,
HackingSupport4,
HackingSupport5,
HackingSupport6,
HackingSupport7,
//230
HeavyAssault1,
HeavyAssault2,
HeavyAssault3,
HeavyAssault4,
HeavyAssault5,
HeavyAssault6,
HeavyAssault7,
HeavyInfantry,
HeavyInfantry2,
HeavyInfantry3,
//240
HeavyInfantry4,
InfantryExpert1,
InfantryExpert2,
InfantryExpert3,
Jacking,
Jacking2,
Jacking3,
Jacking4,
Jacking5,
Jacking6,
//250
Jacking7,
KnifeCombat1,
KnifeCombat2,
KnifeCombat3,
KnifeCombat4,
KnifeCombat5,
KnifeCombat6,
KnifeCombat7,
LightInfantry,
LockerCracker1,
//260
LockerCracker2,
LockerCracker3,
LockerCracker4,
LockerCracker5,
LockerCracker6,
LockerCracker7,
LodestarSupport1,
LodestarSupport2,
LodestarSupport3,
LodestarSupport4,
//270
LodestarSupport5,
LodestarSupport6,
LodestarSupport7,
Loser,
Loser2,
Loser3,
Loser4,
MarkovVeteran,
Max1,
Max2,
//280
Max3,
Max4,
Max5,
Max6,
MaxBuster1,
MaxBuster2,
MaxBuster3,
MaxBuster4,
MaxBuster5,
MaxBuster6,
//290
MediumAssault1,
MediumAssault2,
MediumAssault3,
MediumAssault4,
MediumAssault5,
MediumAssault6,
MediumAssault7,
OneYearNC,
OneYearTR,
OneYearVS,
//300
Orion1,
Orion2,
Orion3,
Orion4,
Orion5,
Orion6,
Orion7,
Osprey1,
Osprey2,
Osprey3,
//310
Osprey4,
Osprey5,
Osprey6,
Osprey7,
Phalanx1,
Phalanx2,
Phalanx3,
Phalanx4,
Phalanx5,
Phalanx6,
//320
Phalanx7,
PSUMaAttendee,
PSUMbAttendee,
QAAppreciation,
ReinforcementHackSpecialist,
ReinforcementInfantrySpecialist,
ReinforcementSpecialist,
ReinforcementVehicleSpecialist,
RouterSupport1,
RouterSupport2,
//330
RouterSupport3,
RouterSupport4,
RouterSupport5,
RouterSupport6,
RouterSupport7,
RouterTelepadDeploy1,
RouterTelepadDeploy2,
RouterTelepadDeploy3,
RouterTelepadDeploy4,
RouterTelepadDeploy5,
//340
RouterTelepadDeploy6,
RouterTelepadDeploy7,
ScavengerNC1,
ScavengerNC2,
ScavengerNC3,
ScavengerNC4,
ScavengerNC5,
ScavengerNC6,
ScavengerTR1,
ScavengerTR2,
//350
ScavengerTR3,
ScavengerTR4,
ScavengerTR5,
ScavengerTR6,
ScavengerVS1,
ScavengerVS2,
ScavengerVS3,
ScavengerVS4,
ScavengerVS5,
ScavengerVS6,
//360
SixYearNC,
SixYearTR,
SixYearVS,
Sniper1,
Sniper2,
Sniper3,
Sniper4,
Sniper5,
Sniper6,
Sniper7,
//370
SpecialAssault1,
SpecialAssault2,
SpecialAssault3,
SpecialAssault4,
SpecialAssault5,
SpecialAssault6,
SpecialAssault7,
StandardAssault1,
StandardAssault2,
StandardAssault3,
//380
StandardAssault4,
StandardAssault5,
StandardAssault6,
StandardAssault7,
StracticsHistorian,
Supply1,
Supply2,
Supply3,
Supply4,
Supply5,
//390
Supply6,
Supply7,
TankBuster1,
TankBuster2,
TankBuster3,
TankBuster4,
TankBuster5,
TankBuster6,
TankBuster7,
ThreeYearNC,
//400
ThreeYearTR,
ThreeYearVS,
TinyRoboticSupport1,
TinyRoboticSupport2,
TinyRoboticSupport3,
TinyRoboticSupport4,
TinyRoboticSupport5,
TinyRoboticSupport6,
TinyRoboticSupport7,
Transport1,
//410
Transport2,
Transport3,
Transport4,
Transport5,
Transport6,
Transport7,
TransportationCitation1,
TransportationCitation2,
TransportationCitation3,
TransportationCitation4,
//420
TransportationCitation5,
TwoYearNC,
TwoYearTR,
TwoYearVS,
ValentineFemale,
ValentineMale,
WernerVeteran,
XmasGingerman,
XmasSnowman,
XmasSpirit
= Value
/*
The value None requires special consideration.
- A Long number is required for this Enumeration codec.
- Enumerations are designed to only handle Int numbers.
- A Codec only handles unsigned numbers.
- The value of MeritCommendation.None is intended to be 0xFFFFFFFF, which (a) is 4294967295 as a Long, but (b) is -1 as an Integer.
- Due to (a), an Enumeration can not be used to represent that number.
- Due to (b), a Codec can not be used to convert to that number.
*/
val None = Value(-1)
/**
* Carefully and explicitly convert between `Codec[Long] -> Long -> Int -> MeritCommendation.Value`.
*/
implicit val codec = uint32L.exmap[MeritCommendation.Value] (
{
case 0xFFFFFFFFL =>
Attempt.successful(MeritCommendation.None)
case n =>
if(n > Int.MaxValue) {
Attempt.failure(Err(s"value $n is too high, above maximum integer value ${Int.MaxValue}"))
}
else {
Attempt.successful(MeritCommendation(n.toInt))
}
},
{
case MeritCommendation.None =>
Attempt.successful(0xFFFFFFFFL)
case enum =>
Attempt.successful(enum.id.toLong)
}
)
}

View file

@ -0,0 +1,30 @@
// Copyright (c) 2017 PSForever
package game
import net.psforever.types.MeritCommendation
import org.specs2.mutable._
import net.psforever.packet._
import net.psforever.packet.game._
import scodec.bits._
class DisplayedAwardMessageTest extends Specification {
val string = hex"D1 9F06 A6010000 3 0"
"decode" in {
PacketCoding.DecodePacket(string).require match {
case DisplayedAwardMessage(player_guid, ribbon, bar) =>
player_guid mustEqual PlanetSideGUID(1695)
ribbon mustEqual MeritCommendation.TwoYearTR
bar mustEqual RibbonBarsSlot.TermOfService
case _ =>
ko
}
}
"encode" in {
val msg = DisplayedAwardMessage(PlanetSideGUID(1695), MeritCommendation.TwoYearTR, RibbonBarsSlot.TermOfService)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string
}
}

View file

@ -201,10 +201,10 @@ class ObjectCreateDetailedMessageTest extends Specification {
char.appearance.is_cloaking mustEqual false char.appearance.is_cloaking mustEqual false
char.appearance.charging_pose mustEqual false char.appearance.charging_pose mustEqual false
char.appearance.on_zipline mustEqual false char.appearance.on_zipline mustEqual false
char.appearance.ribbons.upper mustEqual 0xFFFFFFFFL //none char.appearance.ribbons.upper mustEqual MeritCommendation.None
char.appearance.ribbons.middle mustEqual 0xFFFFFFFFL //none char.appearance.ribbons.middle mustEqual MeritCommendation.None
char.appearance.ribbons.lower mustEqual 0xFFFFFFFFL //none char.appearance.ribbons.lower mustEqual MeritCommendation.None
char.appearance.ribbons.tos mustEqual 0xFFFFFFFFL //none char.appearance.ribbons.tos mustEqual MeritCommendation.None
char.healthMax mustEqual 100 char.healthMax mustEqual 100
char.health mustEqual 100 char.health mustEqual 100
char.armor mustEqual 50 //standard exosuit value char.armor mustEqual 50 //standard exosuit value

View file

@ -4,7 +4,7 @@ package game
import net.psforever.packet._ import net.psforever.packet._
import net.psforever.packet.game._ import net.psforever.packet.game._
import net.psforever.packet.game.objectcreate._ import net.psforever.packet.game.objectcreate._
import net.psforever.types.{CharacterGender, ExoSuitType, GrenadeState, PlanetSideEmpire, Vector3} import net.psforever.types._
import org.specs2.mutable._ import org.specs2.mutable._
import scodec.bits._ import scodec.bits._
@ -684,10 +684,10 @@ class ObjectCreateMessageTest extends Specification {
pc.appearance.is_cloaking mustEqual false pc.appearance.is_cloaking mustEqual false
pc.appearance.charging_pose mustEqual false pc.appearance.charging_pose mustEqual false
pc.appearance.on_zipline mustEqual false pc.appearance.on_zipline mustEqual false
pc.appearance.ribbons.upper mustEqual 276L pc.appearance.ribbons.upper mustEqual MeritCommendation.Loser4
pc.appearance.ribbons.middle mustEqual 239L pc.appearance.ribbons.middle mustEqual MeritCommendation.HeavyInfantry3
pc.appearance.ribbons.lower mustEqual 397L pc.appearance.ribbons.lower mustEqual MeritCommendation.TankBuster6
pc.appearance.ribbons.tos mustEqual 360L pc.appearance.ribbons.tos mustEqual MeritCommendation.SixYearNC
pc.health mustEqual 255 pc.health mustEqual 255
pc.armor mustEqual 253 pc.armor mustEqual 253
pc.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade pc.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade
@ -780,10 +780,10 @@ class ObjectCreateMessageTest extends Specification {
pc.appearance.is_cloaking mustEqual false pc.appearance.is_cloaking mustEqual false
pc.appearance.charging_pose mustEqual false pc.appearance.charging_pose mustEqual false
pc.appearance.on_zipline mustEqual false pc.appearance.on_zipline mustEqual false
pc.appearance.ribbons.upper mustEqual 244L pc.appearance.ribbons.upper mustEqual MeritCommendation.Jacking
pc.appearance.ribbons.middle mustEqual 353L pc.appearance.ribbons.middle mustEqual MeritCommendation.ScavengerTR6
pc.appearance.ribbons.lower mustEqual 33L pc.appearance.ribbons.lower mustEqual MeritCommendation.AMSSupport4
pc.appearance.ribbons.tos mustEqual 361L pc.appearance.ribbons.tos mustEqual MeritCommendation.SixYearTR
pc.health mustEqual 0 pc.health mustEqual 0
pc.armor mustEqual 0 pc.armor mustEqual 0
pc.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade pc.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade
@ -1107,7 +1107,12 @@ class ObjectCreateMessageTest extends Specification {
false, false,
GrenadeState.None, GrenadeState.None,
false, false, false, false, false, false,
RibbonBars(276L, 239L, 397L, 360L) RibbonBars(
MeritCommendation.Loser4,
MeritCommendation.HeavyInfantry3,
MeritCommendation.TankBuster6,
MeritCommendation.SixYearNC
)
), ),
255, 253, 255, 253,
UniformStyle.ThirdUpgrade, UniformStyle.ThirdUpgrade,
@ -1159,7 +1164,12 @@ class ObjectCreateMessageTest extends Specification {
false, false,
GrenadeState.None, GrenadeState.None,
false, false, false, false, false, false,
RibbonBars(244L, 353L, 33L, 361L) RibbonBars(
MeritCommendation.Jacking,
MeritCommendation.ScavengerTR6,
MeritCommendation.AMSSupport4,
MeritCommendation.SixYearTR
)
), ),
0, 0, 0, 0,
UniformStyle.ThirdUpgrade, UniformStyle.ThirdUpgrade,