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

View file

@ -0,0 +1,30 @@
// Copyright (c) 2017 PSForever
package game
import org.specs2.mutable._
import net.psforever.packet._
import net.psforever.packet.game._
import scodec.bits._
class DelayedPathMountMsgTest extends Specification {
val string = hex"5a f50583044680"
"decode" in {
PacketCoding.DecodePacket(string).require match {
case DelayedPathMountMsg(player_guid, vehicle_guid,u3,u4) =>
player_guid mustEqual PlanetSideGUID(1525)
vehicle_guid mustEqual PlanetSideGUID(1155)
u3 mustEqual 70
u4 mustEqual true
case _ =>
ko
}
}
"encode" in {
val msg = DelayedPathMountMsg(PlanetSideGUID(1525), PlanetSideGUID(1155),70,true)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string
}
}

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

@ -0,0 +1,55 @@
// Copyright (c) 2017 PSForever
package game
import org.specs2.mutable._
import net.psforever.packet._
import net.psforever.packet.game._
import net.psforever.types.Vector3
import scodec.bits._
class FireHintMessageTest extends Specification {
val string = hex"a1 0117 23cd63f1d7480d 000077ff9d1d00"
val string2 = hex"a1 080e 65af5705074411 0000cffee0fc7b08899f5580"
"decode" in {
PacketCoding.DecodePacket(string).require match {
case FireHintMessage(weapon_guid, pos, u1, u2, u3, u4, u5) =>
weapon_guid mustEqual PlanetSideGUID(5889)
pos mustEqual Vector3(3482.2734f,3642.4922f,53.125f)
u1 mustEqual 0
u2 mustEqual 65399
u3 mustEqual 7581
u4 mustEqual 0
u5 mustEqual None
case _ =>
ko
}
}
"decode string2" in {
PacketCoding.DecodePacket(string2).require match {
case FireHintMessage(weapon_guid, pos, u1, u2, u3, u4, u5) =>
weapon_guid mustEqual PlanetSideGUID(3592)
pos mustEqual Vector3(2910.789f,3744.875f,69.0625f)
u1 mustEqual 0
u2 mustEqual 65231
u3 mustEqual 64736
u4 mustEqual 3
u5 mustEqual Some(Vector3(21.5f,-6.8125f,2.65625f))
case _ =>
ko
}
}
"encode" in {
val msg = FireHintMessage(PlanetSideGUID(5889), Vector3(3482.2734f,3642.4922f,53.125f), 0, 65399, 7581, 0)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string
}
"encode string2" in {
val msg = FireHintMessage(PlanetSideGUID(3592), Vector3(2910.789f,3744.875f,69.0625f), 0, 65231, 64736, 3, Some(Vector3(21.5f,-6.8125f,2.65625f)))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string2
}
}

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,

View file

@ -0,0 +1,53 @@
// Copyright (c) 2017 PSForever
package game
import org.specs2.mutable._
import net.psforever.packet._
import net.psforever.packet.game._
import scodec.bits._
class OxygenStateMessageTest extends Specification {
val string_self = hex"78 4b00f430"
val string_vehicle = hex"78 4b00f4385037a180"
"decode (self)" in {
PacketCoding.DecodePacket(string_self).require match {
case OxygenStateMessage(guid, progress, active, veh_state) =>
guid mustEqual PlanetSideGUID(75)
progress mustEqual 50.0
active mustEqual true
veh_state.isDefined mustEqual false
case _ =>
ko
}
}
"decode (vehicle)" in {
PacketCoding.DecodePacket(string_vehicle).require match {
case OxygenStateMessage(guid, progress, active, veh_state) =>
guid mustEqual PlanetSideGUID(75)
progress mustEqual 50.0f
active mustEqual true
veh_state.isDefined mustEqual true
veh_state.get.vehicle_guid mustEqual PlanetSideGUID(1546)
veh_state.get.progress mustEqual 50.0f
veh_state.get.active mustEqual true
case _ =>
ko
}
}
"encode (self)" in {
val msg = OxygenStateMessage(PlanetSideGUID(75), 50.0f, true)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_self
}
"encode (vehicle)" in {
val msg = OxygenStateMessage(PlanetSideGUID(75), 50.0f, true, WaterloggedVehicleState(PlanetSideGUID(1546), 50.0f, true))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_vehicle
}
}

View file

@ -0,0 +1,44 @@
// Copyright (c) 2017 PSForever
package game
import org.specs2.mutable._
import net.psforever.packet._
import net.psforever.packet.game._
import net.psforever.types.Vector3
import scodec.bits._
class ProjectileStateMessageTest extends Specification {
val string = hex"3f 259d c5019 30e4a 9514 c52c9541 d9ba05c2 c5973941 00 f8 ec 020000"
"decode" in {
PacketCoding.DecodePacket(string).require match {
case ProjectileStateMessage(projectile, pos, vel, unk1, unk2, unk3, unk4, time_alive) =>
projectile mustEqual PlanetSideGUID(40229)
pos.x mustEqual 4611.539f
pos.y mustEqual 5576.375f
pos.z mustEqual 82.328125f
vel.x mustEqual 18.64686f
vel.y mustEqual -33.43247f
vel.z mustEqual 11.599553f
unk1 mustEqual 0
unk2 mustEqual 248
unk3 mustEqual 236
unk4 mustEqual false
time_alive mustEqual 4
case _ =>
ko
}
}
"encode" in {
val msg = ProjectileStateMessage(
PlanetSideGUID(40229),
Vector3(4611.539f, 5576.375f, 82.328125f),
Vector3(18.64686f, -33.43247f, 11.599553f),
0, 248, 236, false, 4
)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string
}
}

View file

@ -0,0 +1,116 @@
// Copyright (c) 2017 PSForever
package game
import org.specs2.mutable._
import net.psforever.packet._
import net.psforever.packet.game._
import scodec.bits._
class TargetingImplantRequestTest extends Specification {
val string_single = hex"b5 061016"
val string_long = hex"b5 41edeb12d4409f0144053f8010541ba91d03df376831b1e26000611041e1107c0209c0"//0510085013d9ffb6720d5b132900003770?
"decode (single)" in {
PacketCoding.DecodePacket(string_single).require match {
case TargetingImplantRequest(target_list) =>
target_list.length mustEqual 1
//0
target_list.head.target_guid mustEqual PlanetSideGUID(1412)
target_list.head.unk mustEqual true
case _ =>
ko
}
}
"decode (long)" in {
PacketCoding.DecodePacket(string_long).require match {
case TargetingImplantRequest(target_list) =>
target_list.length mustEqual 16
//0
target_list.head.target_guid mustEqual PlanetSideGUID(31355)
target_list.head.unk mustEqual true
//1
target_list(1).target_guid mustEqual PlanetSideGUID(27273)
target_list(1).unk mustEqual false
//2
target_list(2).target_guid mustEqual PlanetSideGUID(40768)
target_list(2).unk mustEqual false
//3
target_list(3).target_guid mustEqual PlanetSideGUID(34818)
target_list(3).unk mustEqual false
//4
target_list(4).target_guid mustEqual PlanetSideGUID(65044)
target_list(4).unk mustEqual false
//5
target_list(5).target_guid mustEqual PlanetSideGUID(33280)
target_list(5).unk mustEqual true
//6
target_list(6).target_guid mustEqual PlanetSideGUID(47681)
target_list(6).unk mustEqual true
//7
target_list(7).target_guid mustEqual PlanetSideGUID(40995)
target_list(7).unk mustEqual false
//8
target_list(8).target_guid mustEqual PlanetSideGUID(52727)
target_list(8).unk mustEqual true
//9
target_list(9).target_guid mustEqual PlanetSideGUID(6324)
target_list(9).unk mustEqual true
//10
target_list(10).target_guid mustEqual PlanetSideGUID(58033)
target_list(10).unk mustEqual false
//11
target_list(11).target_guid mustEqual PlanetSideGUID(192)
target_list(11).unk mustEqual true
//12
target_list(12).target_guid mustEqual PlanetSideGUID(16772)
target_list(12).unk mustEqual false
//13
target_list(13).target_guid mustEqual PlanetSideGUID(2063)
target_list(13).unk mustEqual true
//14
target_list(14).target_guid mustEqual PlanetSideGUID(49159)
target_list(14).unk mustEqual false
//15
target_list(15).target_guid mustEqual PlanetSideGUID(14401)
target_list(15).unk mustEqual false
case _ =>
ko
}
}
"encode (single)" in {
val msg = TargetingImplantRequest(
TargetRequest(PlanetSideGUID(1412), true) ::
Nil
)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_single
}
"encode (long)" in {
val msg = TargetingImplantRequest(
TargetRequest(PlanetSideGUID(31355), true) ::
TargetRequest(PlanetSideGUID(27273), false) ::
TargetRequest(PlanetSideGUID(40768), false) ::
TargetRequest(PlanetSideGUID(34818), false) ::
TargetRequest(PlanetSideGUID(65044), false) ::
TargetRequest(PlanetSideGUID(33280), true) ::
TargetRequest(PlanetSideGUID(47681), true) ::
TargetRequest(PlanetSideGUID(40995), false) ::
TargetRequest(PlanetSideGUID(52727), true) ::
TargetRequest(PlanetSideGUID(6324), true) ::
TargetRequest(PlanetSideGUID(58033), false) ::
TargetRequest(PlanetSideGUID(192), true) ::
TargetRequest(PlanetSideGUID(16772), false) ::
TargetRequest(PlanetSideGUID(2063), true) ::
TargetRequest(PlanetSideGUID(49159), false) ::
TargetRequest(PlanetSideGUID(14401), false) ::
Nil
)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_long
}
}

View file

@ -0,0 +1,73 @@
// Copyright (c) 2017 PSForever
package game
import org.specs2.mutable._
import net.psforever.packet._
import net.psforever.packet.game._
import net.psforever.types.Vector3
import scodec.bits._
class TriggerEffectMessageTest extends Specification {
val string_motionalarmsensor = hex"51 970B 82 6F6E FA00C00000"
val string_boomer = hex"51 0000 93 737061776E5F6F626A6563745F656666656374 417BB2CB3B4F8E00000000"
"decode (motion alarm sensor)" in {
PacketCoding.DecodePacket(string_motionalarmsensor).require match {
case TriggerEffectMessage(guid, effect, unk, location) =>
guid mustEqual PlanetSideGUID(2967)
effect mustEqual "on"
unk.isDefined mustEqual true
unk.get.unk1 mustEqual true
unk.get.unk2 mustEqual 1000L
location.isDefined mustEqual false
case _ =>
ko
}
}
"decode (boomer)" in {
PacketCoding.DecodePacket(string_boomer).require match {
case TriggerEffectMessage(guid, effect, unk, location) =>
guid mustEqual PlanetSideGUID(0)
effect mustEqual "spawn_object_effect"
unk.isDefined mustEqual false
location.isDefined mustEqual true
location.get.pos.x mustEqual 3567.0156f
location.get.pos.y mustEqual 3278.6953f
location.get.pos.z mustEqual 114.484375f
location.get.roll mustEqual 0
location.get.pitch mustEqual 0
location.get.yaw mustEqual 0
case _ =>
ko
}
}
"encode (motion alarm sensor)" in {
val msg = TriggerEffectMessage(
PlanetSideGUID(2967),
"on",
Some(TriggeredEffect(true, 1000L)),
None
)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_motionalarmsensor
}
"encode (boomer)" in {
val msg = TriggerEffectMessage(
PlanetSideGUID(0),
"spawn_object_effect",
None,
Some(TriggeredEffectLocation(
Vector3(3567.0156f, 3278.6953f, 114.484375f),
0, 0, 0
))
)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_boomer
}
}

View file

@ -0,0 +1,28 @@
// Copyright (c) 2017 PSForever
package game
import org.specs2.mutable._
import net.psforever.packet._
import net.psforever.packet.game._
import scodec.bits._
class UnuseItemMessageTest extends Specification {
val string = hex"26 4B00 340D"
"decode" in {
PacketCoding.DecodePacket(string).require match {
case UnuseItemMessage(player, item) =>
player mustEqual PlanetSideGUID(75)
item mustEqual PlanetSideGUID(3380)
case _ =>
ko
}
}
"encode" in {
val msg = UnuseItemMessage(PlanetSideGUID(75), PlanetSideGUID(3380))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string
}
}

View file

@ -255,6 +255,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ ChildObjectStateMessage(object_guid : PlanetSideGUID, pitch : Int, yaw : Int) =>
//log.info("ChildObjectState: " + msg)
case msg @ ProjectileStateMessage(projectile_guid, shot_pos, shot_vector, unk1, unk2, unk3, unk4, time_alive) =>
//log.info("ProjectileState: " + msg)
case msg @ ChatMsg(messagetype, has_wide_contents, recipient, contents, note_contents) =>
// TODO: Prevents log spam, but should be handled correctly
if (messagetype != ChatMessageType.CMT_TOGGLE_GM) {
@ -363,6 +366,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
// TODO: This should only actually be sent to doors upon opening; may break non-door items upon use
sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectStateMsg(object_guid, 16)))
}
case msg @ UnuseItemMessage(player, item) =>
log.info("UnuseItem: " + msg)
case msg @ DeployObjectMessage(guid, unk1, pos, roll, pitch, yaw, unk2) =>
log.info("DeployObject: " + msg)
@ -440,6 +446,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ WeaponDryFireMessage(weapon) =>
log.info("WeaponDryFireMessage: "+msg)
case msg @ TargetingImplantRequest(list) =>
log.info("TargetingImplantRequest: "+msg)
case default => log.error(s"Unhandled GamePacket ${pkt}")
}