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
case 0xd0 => noDecoder(UnknownMessage208)
case 0xd1 => noDecoder(DisplayedAwardMessage)
case 0xd1 => game.DisplayedAwardMessage.decode
case 0xd2 => noDecoder(RespawnAMSInfoMessage)
case 0xd3 => noDecoder(ComponentDamageMessage)
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
* @return an ObjectCreateMessage
*/
def apply(objectClass : Int, guid : PlanetSideGUID, parentInfo : ObjectCreateMessageParent, data : ConstructorData) : ObjectCreateDetailedMessage =
ObjectCreateDetailedMessage(0L, objectClass, guid, Some(parentInfo), Some(data))
def apply(objectClass : Int, guid : PlanetSideGUID, parentInfo : ObjectCreateMessageParent, data : ConstructorData) : ObjectCreateDetailedMessage = {
val parentInfoOpt : Option[ObjectCreateMessageParent] = Some(parentInfo)
ObjectCreateDetailedMessage(ObjectCreateBase.streamLen(parentInfoOpt, data), objectClass, guid, parentInfoOpt, Some(data))
}
/**
* 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
* @return an ObjectCreateMessage
*/
def apply(objectClass : Int, guid : PlanetSideGUID, data : ConstructorData) : ObjectCreateDetailedMessage =
ObjectCreateDetailedMessage(0L, objectClass, guid, None, Some(data))
def apply(objectClass : Int, guid : PlanetSideGUID, data : ConstructorData) : ObjectCreateDetailedMessage = {
ObjectCreateDetailedMessage(ObjectCreateBase.streamLen(None, data), objectClass, guid, None, Some(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
import net.psforever.packet.Marshallable
import net.psforever.types.MeritCommendation
import scodec.Codec
import scodec.codecs._
/**
* 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>
* <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.
* These are the medals players wish to brandish on their left pauldron.
* @param upper the "top" configurable merit ribbon
* @param middle the central configurable merit ribbon
* @param lower the lower configurable merit ribbon
* @param tos the top-most term of service merit ribbon
* @see `MeritCommendation`
* @see `DisplayedAwardMessage`
*/
final case class RibbonBars(upper : Long = RibbonBars.noRibbon,
middle : Long = RibbonBars.noRibbon,
lower : Long = RibbonBars.noRibbon,
tos : Long = RibbonBars.noRibbon) extends StreamBitSize {
final case class RibbonBars(upper : MeritCommendation.Value = MeritCommendation.None,
middle : MeritCommendation.Value = MeritCommendation.None,
lower : MeritCommendation.Value = MeritCommendation.None,
tos : MeritCommendation.Value = MeritCommendation.None
) extends StreamBitSize {
override def bitsize : Long = 128L
}
object RibbonBars extends Marshallable[RibbonBars] {
val noRibbon : Long = 0xFFFFFFFFL
implicit val codec : Codec[RibbonBars] = (
("upper" | uint32L) ::
("middle" | uint32L) ::
("lower" | uint32L) ::
("tos" | uint32L)
("upper" | MeritCommendation.codec) ::
("middle" | MeritCommendation.codec) ::
("lower" | MeritCommendation.codec) ::
("tos" | MeritCommendation.codec)
).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.charging_pose mustEqual false
char.appearance.on_zipline mustEqual false
char.appearance.ribbons.upper mustEqual 0xFFFFFFFFL //none
char.appearance.ribbons.middle mustEqual 0xFFFFFFFFL //none
char.appearance.ribbons.lower mustEqual 0xFFFFFFFFL //none
char.appearance.ribbons.tos mustEqual 0xFFFFFFFFL //none
char.appearance.ribbons.upper mustEqual MeritCommendation.None
char.appearance.ribbons.middle mustEqual MeritCommendation.None
char.appearance.ribbons.lower mustEqual MeritCommendation.None
char.appearance.ribbons.tos mustEqual MeritCommendation.None
char.healthMax mustEqual 100
char.health mustEqual 100
char.armor mustEqual 50 //standard exosuit value

View file

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