Merge branch 'master' into minor-updates

This commit is contained in:
Fate-JH 2017-05-13 11:45:11 -04:00 committed by GitHub
commit f87edd7057
23 changed files with 1474 additions and 42 deletions

View file

@ -363,7 +363,7 @@ object GamePacketOpcode extends Enumeration {
case 0x23 => noDecoder(ActionCancelAcknowledgeMessage)
case 0x24 => game.SetEmpireMessage.decode
case 0x25 => game.EmoteMsg.decode
case 0x26 => noDecoder(UnuseItemMessage)
case 0x26 => game.UnuseItemMessage.decode
case 0x27 => game.ObjectDetachMessage.decode
// 0x28
case 0x28 => game.CreateShortcutMessage.decode
@ -392,7 +392,7 @@ object GamePacketOpcode extends Enumeration {
case 0x3c => game.GenericCollisionMsg.decode
case 0x3d => game.QuantityUpdateMessage.decode
case 0x3e => game.ArmorChangedMessage.decode
case 0x3f => noDecoder(ProjectileStateMessage)
case 0x3f => game.ProjectileStateMessage.decode
// OPCODES 0x40-4f
case 0x40 => noDecoder(MountVehicleCargoMsg)
@ -415,7 +415,7 @@ object GamePacketOpcode extends Enumeration {
// OPCODES 0x50-5f
case 0x50 => game.TargetingInfoMessage.decode
case 0x51 => noDecoder(TriggerEffectMessage)
case 0x51 => game.TriggerEffectMessage.decode
case 0x52 => game.WeaponDryFireMessage.decode
case 0x53 => noDecoder(DroppodLaunchRequestMessage)
case 0x54 => noDecoder(HackMessage)
@ -425,7 +425,7 @@ object GamePacketOpcode extends Enumeration {
// 0x58
case 0x58 => game.AvatarImplantMessage.decode
case 0x59 => noDecoder(UnknownMessage89)
case 0x5a => noDecoder(DelayedPathMountMsg)
case 0x5a => game.DelayedPathMountMsg.decode
case 0x5b => noDecoder(OrbitalShuttleTimeMsg)
case 0x5c => noDecoder(AIDamage)
case 0x5d => game.DeployObjectMessage.decode
@ -461,7 +461,7 @@ object GamePacketOpcode extends Enumeration {
case 0x76 => game.DeployableObjectsInfoMessage.decode
case 0x77 => noDecoder(SquadState)
// 0x78
case 0x78 => noDecoder(OxygenStateMessage)
case 0x78 => game.OxygenStateMessage.decode
case 0x79 => noDecoder(TradeMessage)
case 0x7a => noDecoder(UnknownMessage122)
case 0x7b => noDecoder(DamageFeedbackMessage)
@ -510,7 +510,7 @@ object GamePacketOpcode extends Enumeration {
// OPCODES 0xa0-af
case 0xa0 => game.BuildingInfoUpdateMessage.decode
case 0xa1 => noDecoder(FireHintMessage)
case 0xa1 => game.FireHintMessage.decode
case 0xa2 => noDecoder(UplinkRequest)
case 0xa3 => noDecoder(UplinkResponse)
case 0xa4 => game.WarpgateRequest.decode
@ -533,7 +533,7 @@ object GamePacketOpcode extends Enumeration {
case 0xb2 => game.VoiceHostInfo.decode
case 0xb3 => game.BattleplanMessage.decode
case 0xb4 => game.BattleExperienceMessage.decode
case 0xb5 => noDecoder(TargetingImplantRequest)
case 0xb5 => game.TargetingImplantRequest.decode
case 0xb6 => game.ZonePopulationUpdateMessage.decode
case 0xb7 => game.DisconnectMessage.decode
// 0xb8
@ -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,32 @@
// Copyright (c) 2016 PSForever.net to present
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import scodec.Codec
import scodec.codecs._
/**
* na
* @param player_guid na
* @param vehicle_guid vehicle ?, turret ? Found a HART GUID for now. Need more search.
* @param u1 na - maybe a delay ?
* @param u2 na
*/
final case class DelayedPathMountMsg(player_guid : PlanetSideGUID,
vehicle_guid : PlanetSideGUID,
u1 : Int,
u2 : Boolean)
extends PlanetSideGamePacket {
type Packet = DelayedPathMountMsg
def opcode = GamePacketOpcode.DelayedPathMountMsg
def encode = DelayedPathMountMsg.encode(this)
}
object DelayedPathMountMsg extends Marshallable[DelayedPathMountMsg] {
implicit val codec : Codec[DelayedPathMountMsg] = (
("player_guid" | PlanetSideGUID.codec) ::
("vehicle_guid" | PlanetSideGUID.codec) ::
("u1" | uint8L) ::
("u2" | bool)
).as[DelayedPathMountMsg]
}

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

@ -0,0 +1,44 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import net.psforever.types.Vector3
import scodec.Codec
import scodec.codecs._
/**
* not sure for u1 / u2 / u3, maybe need a real brain ...
* @param weapon_guid na
* @param pos na; pos of what ?!
* @param u1 na
* @param u2 na
* @param u3 na
* @param u4 na
* @param u5 na; vel of what ?!
*/
final case class FireHintMessage(weapon_guid : PlanetSideGUID,
pos : Vector3,
u1 : Int,
u2 : Int,
u3 : Int,
u4 : Int,
u5 : Option[Vector3] = None)
extends PlanetSideGamePacket {
type Packet = FireHintMessage
def opcode = GamePacketOpcode.FireHintMessage
def encode = FireHintMessage.encode(this)
}
object FireHintMessage extends Marshallable[FireHintMessage] {
implicit val codec : Codec[FireHintMessage] = (
("weapon_guid" | PlanetSideGUID.codec) ::
("pos" | Vector3.codec_pos) ::
("u1" | uint16L) ::
("u2" | uint16L) ::
("u3" | uint16L) ::
("u4" | uintL(3)) ::
optional(bool, "u5" | Vector3.codec_vel)
).as[FireHintMessage]
}

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

@ -0,0 +1,111 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
import net.psforever.newcodecs.newcodecs
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import scodec.{Attempt, Codec}
import scodec.codecs._
import shapeless.{::, HNil}
/**
* Alert the condition of a vehicle the player is using when going too far underwater.
* The player must be mounted in/on this vehicle at start time for this countdown to display.
* @param vehicle_guid the player's mounted vehicle
* @param progress the remaining countdown;
* for vehicle waterlog condition, the progress per second rate is very high
* @param active show a new countdown if `true` (resets any active countdown);
* clear any active countdowns if `false`;
* defaults to `true`
*/
final case class WaterloggedVehicleState(vehicle_guid : PlanetSideGUID,
progress : Float,
active : Boolean = true)
/**
* Dispatched by the server to cause the player to slowly drown.
* If the player is mounted in a vehicle at the time, alert the player that the vehicle may be disabled.<br>
* <br>
* When a player walks too far underwater, a borderless red progress bar with a countdown from 100 (98) is displayed across the screen.
* The countdown proceeds to zero at a fixed rate and is timed with the depleting progress bar.
* When it reaches zero, the player will be killed.
* If the player is in a vehicle after a certain depth, a blue bar and countdown pair will superimpose the red indicators.
* It depletes much more rapidly than the red indicators.
* When it reaches zero, the vehicle will become disabled.
* All players in the vehicle's seats will be kicked and they will not be allowed back in.<br>
* <br>
* Normally, the countdowns should be set to begin at 100 (100.0).
* This is the earliest the drowning GUI will appear for either blue or red indicators.
* Passing greater intervals - up to 204.8 - will start the countdown silently but the GUI will be hidden until 100.0.
* (The progress indicators will actually appear to start counting from 98.)
* Managing the secondary vehicle countdown independent of the primary player countdown requires updating with the correct levels.
* The countdown can be cancelled by instructing it to be `active = false`.<br>
* <br>
* Except for updating the indicators, all other functionality of "drowning" is automated by the server.
* @param player_guid the player
* @param progress the remaining countdown;
* for character oxygen, the progress per second rate is about 1
* @param active show a new countdown if `true` (resets any active countdown);
* clear any active countdowns if `false`
* @param vehicle_state optional state of the vehicle the player is driving
*/
final case class OxygenStateMessage(player_guid : PlanetSideGUID,
progress : Float,
active : Boolean,
vehicle_state : Option[WaterloggedVehicleState] = None)
extends PlanetSideGamePacket {
type Packet = OxygenStateMessage
def opcode = GamePacketOpcode.OxygenStateMessage
def encode = OxygenStateMessage.encode(this)
}
object OxygenStateMessage extends Marshallable[OxygenStateMessage] {
/**
* Overloaded constructor that removes the optional state of the `WaterloggedVehicleState` parameter.
* @param player_guid the player
* @param progress the remaining countdown
* @param active show or clear the countdown
* @param vehicle_state state of the vehicle the player is driving
* @return
*/
def apply(player_guid : PlanetSideGUID, progress : Float, active : Boolean, vehicle_state : WaterloggedVehicleState) : OxygenStateMessage =
OxygenStateMessage(player_guid, progress, active, Some(vehicle_state))
/**
* A simple pattern that expands the datatypes of the packet's basic `Codec`.
*/
private type basePattern = PlanetSideGUID :: Float :: Boolean :: HNil
/**
* A `Codec` for the repeated processing of three values.
* This `Codec` is the basis for the packet's data.
*/
private val base_codec : Codec[basePattern] =
PlanetSideGUID.codec ::
newcodecs.q_float(0.0f, 204.8f, 11) :: //hackish: 2^11 == 2047, so it should be 204.7; but, 204.8 allows decode == encode
bool
implicit val codec : Codec[OxygenStateMessage] = (
base_codec.exmap[basePattern] (
{
case guid :: time :: active :: HNil =>
Attempt.successful(guid :: time :: active :: HNil)
},
{
case guid :: time :: active :: HNil =>
Attempt.successful(guid :: time :: active :: HNil)
}
) :+
optional(bool,
"vehicle_state" | base_codec.exmap[WaterloggedVehicleState] (
{
case guid :: time :: active :: HNil =>
Attempt.successful(WaterloggedVehicleState(guid, time, active))
},
{
case WaterloggedVehicleState(guid, time, active) =>
Attempt.successful(guid :: time :: active :: HNil)
}
).as[WaterloggedVehicleState]
)
).as[OxygenStateMessage]
}

View file

@ -0,0 +1,65 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import net.psforever.types.Vector3
import scodec.Codec
import scodec.codecs._
/**
* Dispatched to deliberately render certain projectiles of a weapon on other players' clients.<br>
* <br>
* This packet is generated by firing specific weapons in specific fire modes.
* For example, the Phoenix (`hunterseeker`) discharged in its primary fire mode generates this packet;
* but, the Phoenix in secondary fire mode does not.
* The Striker (`striker`) discharged in its primary fire mode generates this packet;
* but, the Striker in secondary fire mode does not.
* The chosen fire mode(s) are not a straight-fire projectile but one that has special control asserted over it.
* For the Phoenix, it is user-operated.
* For the Striker, it tracks towards a target while the weapon's reticle hovers over that target.<br>
* <br>
* This packet will continue to be dispatched by the client for as long as the projectile being tracked is in the air.
* All projectiles have a maximum lifespan before they will lose control and either despawn and/or explode.
* This number is tracked in the packet for simplicity.
* If the projectile strikes a valid target, the count will jump to a significantly enormous value beyond its normal lifespan.
* This ensures that the projectile - locally and the shared model - will despawn.
* @param projectile_guid the projectile
* @param shot_pos the position of the projectile
* @param shot_vel the velocity of the projectile
* @param unk1 na;
* usually 0
* @param unk2 na;
* will remain consistent for the lifespan of a given projectile in most cases
* @param unk3 na;
* will remain consistent for the lifespan of a given projectile in most cases
* @param unk4 na;
* usually false
* @param time_alive how long the projectile has been in the air;
* often expressed in multiples of 2
*/
final case class ProjectileStateMessage(projectile_guid : PlanetSideGUID,
shot_pos : Vector3,
shot_vel : Vector3,
unk1 : Int,
unk2 : Int,
unk3 : Int,
unk4 : Boolean,
time_alive : Int)
extends PlanetSideGamePacket {
type Packet = ProjectileStateMessage
def opcode = GamePacketOpcode.ProjectileStateMessage
def encode = ProjectileStateMessage.encode(this)
}
object ProjectileStateMessage extends Marshallable[ProjectileStateMessage] {
implicit val codec : Codec[ProjectileStateMessage] = (
("projectile_guid" | PlanetSideGUID.codec) ::
("shot_pos" | Vector3.codec_pos) ::
("shot_vel" | Vector3.codec_float) ::
("unk1" | uint8L) ::
("unk2" | uint8L) ::
("unk3" | uint8L) ::
("unk4" | bool) ::
("time_alive" | uint16L)
).as[ProjectileStateMessage]
}

View file

@ -0,0 +1,36 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import scodec.Codec
import scodec.codecs._
/**
* An entry regarding a specific target.
* @param target_guid the target
* @param unk na
*/
final case class TargetRequest(target_guid : PlanetSideGUID,
unk : Boolean)
/**
* Dispatched by the client when the advanced targeting implant activates to collect status information from the server.<br>
* <br>
* This packet is answered by a `TargetingInfoMessage` with `List` entries of thed corresponding UIDs.
* @param target_list a `List` of targets
*/
final case class TargetingImplantRequest(target_list : List[TargetRequest])
extends PlanetSideGamePacket {
type Packet = TargetingImplantRequest
def opcode = GamePacketOpcode.TargetingImplantRequest
def encode = TargetingImplantRequest.encode(this)
}
object TargetingImplantRequest extends Marshallable[TargetingImplantRequest] {
private val request_codec : Codec[TargetRequest] = (
("target_guid" | PlanetSideGUID.codec) ::
("unk" | bool)
).as[TargetRequest]
implicit val codec : Codec[TargetingImplantRequest] = ("target_list" | listOfN(intL(6), request_codec)).as[TargetingImplantRequest]
}

View file

@ -0,0 +1,83 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import net.psforever.types.Vector3
import scodec.Codec
import scodec.codecs._
/**
* na
* @param unk1 na;
* `true` to apply the effect usually
* @param unk2 na
*/
final case class TriggeredEffect(unk1 : Boolean,
unk2 : Long)
/**
* Activate an effect that is not directly associated with an existing game object.
* Without a game object from which to inherit position and orientation, those explicit parameters must be provided.
* @param pos the position in the game world
* @param roll the amount of roll that affects orientation
* @param pitch the amount of pitch that affects orientation
* @param yaw the amount of yaw that affects orientation
*/
final case class TriggeredEffectLocation(pos : Vector3,
roll : Int,
pitch : Int,
yaw : Int)
/**
* Dispatched by the server to cause a client to display a special graphical effect.<br>
* <br>
* The effect being triggered can be based around a specific game object or replayed freely, absent of an anchoring object.
* If object-based then the kinds of effects that can be activated are specific to the object.
* If unbound, then a wider range of effects can be displayed.
* Regardless, one category will rarely ever be activated under the same valid conditions of the other category.
* For example, the effect "on" will only work on objects that accept "on" normally, like a deployed `motionalarmsensor`.
* The effect "spawn_object_effect" can be applied anywhere in the environment;
* but, it can not be activated in conjunction with an existing object.
* @param obj an object that accepts the effect
* @param effect the name of the effect
* @param unk na;
* when activating an effect on an existing object
* @param location an optional position where the effect will be displayed;
* when activating an effect independently
*/
final case class TriggerEffectMessage(obj : PlanetSideGUID,
effect : String,
unk : Option[TriggeredEffect] = None,
location : Option[TriggeredEffectLocation] = None
) extends PlanetSideGamePacket {
type Packet = TriggerEffectMessage
def opcode = GamePacketOpcode.TriggerEffectMessage
def encode = TriggerEffectMessage.encode(this)
}
object TriggerEffectMessage extends Marshallable[TriggerEffectMessage] {
/**
* A `Codec` for `TriggeredEffect` data.
*/
private val effect_codec : Codec[TriggeredEffect] = (
("unk1" | bool) ::
("unk2" | uint32L)
).as[TriggeredEffect]
/**
* A `Codec` for `TriggeredEffectLocation` data.
*/
private val effect_location_codec : Codec[TriggeredEffectLocation] = (
("pos" | Vector3.codec_pos) ::
("roll" | uint8L) ::
("pitch" | uint8L) ::
("yaw" | uint8L)
).as[TriggeredEffectLocation]
implicit val codec : Codec[TriggerEffectMessage] = (
("obj" | PlanetSideGUID.codec) >>:~ { obj =>
("effect" | PacketHelpers.encodedString) ::
optional(bool, "unk" | effect_codec) ::
conditional(obj.guid == 0, "location" | effect_location_codec)
}).as[TriggerEffectMessage]
}

View file

@ -0,0 +1,30 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import scodec.Codec
import scodec.codecs._
/**
* Dispatched by the client when its player is done using something.<br>
* <br>
* The common example is sifting through backpacks, an activity that only one player is allowed to do at a time.
* When a backpack is accessed by one player, other players are blocked.
* When the first player is done accessing the backpack, this packet informs the server so other players may be allowed access.
* @param player_guid the player
* @param item_guid the item
*/
final case class UnuseItemMessage(player_guid : PlanetSideGUID,
item_guid : PlanetSideGUID)
extends PlanetSideGamePacket {
type Packet = UnuseItemMessage
def opcode = GamePacketOpcode.UnuseItemMessage
def encode = UnuseItemMessage.encode(this)
}
object UnuseItemMessage extends Marshallable[UnuseItemMessage] {
implicit val codec : Codec[UnuseItemMessage] = (
("player_guid" | PlanetSideGUID.codec) ::
("item_guid" | PlanetSideGUID.codec)
).as[UnuseItemMessage]
}

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)
}
)
}