mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-04-25 05:45:23 +00:00
Merge branch 'master' into continents
This commit is contained in:
commit
bf178a1b34
16 changed files with 842 additions and 398 deletions
|
|
@ -162,6 +162,36 @@ object GlobalDefinitions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Using the definition for a piece of `Equipment` determine if it is a grenade-type weapon.
|
||||||
|
* Only the normal grenades count; the grenade packs are excluded.
|
||||||
|
* @param edef the `EquipmentDefinition` of the item
|
||||||
|
* @return `true`, if it is a grenade-type weapon; `false`, otherwise
|
||||||
|
*/
|
||||||
|
def isGrenade(edef : EquipmentDefinition) : Boolean = {
|
||||||
|
edef match {
|
||||||
|
case `frag_grenade` | `jammer_grenade` | `plasma_grenade` =>
|
||||||
|
true
|
||||||
|
case _ =>
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Using the definition for a piece of `Equipment` determine if it is a grenade-type weapon.
|
||||||
|
* Only the grenade packs count; the normal grenades are excluded.
|
||||||
|
* @param edef the `EquipmentDefinition` of the item
|
||||||
|
* @return `true`, if it is a grenade-type weapon; `false`, otherwise
|
||||||
|
*/
|
||||||
|
def isGrenadePack(edef : EquipmentDefinition) : Boolean = {
|
||||||
|
edef match {
|
||||||
|
case `frag_cartridge` | `jammer_cartridge` | `plasma_cartridge` =>
|
||||||
|
true
|
||||||
|
case _ =>
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Using the definition for a piece of `Equipment` determine with which faction it aligns if it is a weapon.
|
* Using the definition for a piece of `Equipment` determine with which faction it aligns if it is a weapon.
|
||||||
* Only checks `Tool` objects.
|
* Only checks `Tool` objects.
|
||||||
|
|
@ -242,6 +272,43 @@ object GlobalDefinitions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Implants
|
||||||
|
*/
|
||||||
|
val
|
||||||
|
advanced_regen = ImplantDefinition(0)
|
||||||
|
|
||||||
|
val
|
||||||
|
targeting = ImplantDefinition(1)
|
||||||
|
|
||||||
|
val
|
||||||
|
audio_amplifier = ImplantDefinition(2)
|
||||||
|
|
||||||
|
val
|
||||||
|
darklight_vision = ImplantDefinition(3)
|
||||||
|
|
||||||
|
val
|
||||||
|
melee_booster = ImplantDefinition(4)
|
||||||
|
|
||||||
|
val
|
||||||
|
personal_shield = ImplantDefinition(5)
|
||||||
|
|
||||||
|
val
|
||||||
|
range_magnifier = ImplantDefinition(6)
|
||||||
|
|
||||||
|
val
|
||||||
|
second_wind = ImplantDefinition(7)
|
||||||
|
|
||||||
|
val
|
||||||
|
silent_run = ImplantDefinition(8)
|
||||||
|
|
||||||
|
val
|
||||||
|
surge = ImplantDefinition(9)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Equipment (locker_container, kits, ammunition, weapons)
|
||||||
|
*/
|
||||||
|
import net.psforever.packet.game.objectcreate.ObjectClass
|
||||||
val
|
val
|
||||||
locker_container = new EquipmentDefinition(456) {
|
locker_container = new EquipmentDefinition(456) {
|
||||||
Name = "locker container"
|
Name = "locker container"
|
||||||
|
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
// Copyright (c) 2017 PSForever
|
|
||||||
package net.psforever.objects
|
|
||||||
|
|
||||||
import net.psforever.objects.definition.{ImplantDefinition, Stance}
|
|
||||||
import net.psforever.types.{ExoSuitType, ImplantType}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A type of installable player utility that grants a perk, usually in exchange for stamina (energy).<br>
|
|
||||||
* <br>
|
|
||||||
* An implant starts with a never-to-initialized timer value of -1 and will not report as `Ready` until the timer is 0.
|
|
||||||
* The `Timer`, however, will report to the user a time of 0 since negative time does not make sense.
|
|
||||||
* Although the `Timer` can be manually set, using `Reset` is the better way to default the initialization timer to the correct amount.
|
|
||||||
* An external script will be necessary to operate the actual initialization countdown.
|
|
||||||
* An implant must be `Ready` before it can be `Active`.
|
|
||||||
* The `Timer` must be set (or reset) (or countdown) to 0 to be `Ready` and then it must be activated.
|
|
||||||
* @param implantDef the `ObjectDefinition` that constructs this item and maintains some of its immutable fields
|
|
||||||
*/
|
|
||||||
class Implant(implantDef : ImplantDefinition) {
|
|
||||||
private var active : Boolean = false
|
|
||||||
private var initTimer : Long = -1L
|
|
||||||
|
|
||||||
def Name : String = implantDef.Name
|
|
||||||
|
|
||||||
def Ready : Boolean = initTimer == 0L
|
|
||||||
|
|
||||||
def Active : Boolean = active
|
|
||||||
|
|
||||||
def Active_=(isActive : Boolean) : Boolean = {
|
|
||||||
active = Ready && isActive
|
|
||||||
Active
|
|
||||||
}
|
|
||||||
|
|
||||||
def Timer : Long = math.max(0, initTimer)
|
|
||||||
|
|
||||||
def Timer_=(time : Long) : Long = {
|
|
||||||
initTimer = math.max(0, time)
|
|
||||||
Timer
|
|
||||||
}
|
|
||||||
|
|
||||||
def MaxTimer : Long = implantDef.Initialization
|
|
||||||
|
|
||||||
def ActivationCharge : Int = Definition.ActivationCharge
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate the stamina consumption of the implant for any given moment of being active after its activation.
|
|
||||||
* As implant energy use can be influenced by both exo-suit worn and general stance held, both are considered.
|
|
||||||
* @param suit the exo-suit being worn
|
|
||||||
* @param stance the player's stance
|
|
||||||
* @return the amount of stamina (energy) that is consumed
|
|
||||||
*/
|
|
||||||
def Charge(suit : ExoSuitType.Value, stance : Stance.Value) : Int = {
|
|
||||||
if(active) {
|
|
||||||
implantDef.DurationChargeBase + implantDef.DurationChargeByExoSuit(suit) + implantDef.DurationChargeByStance(stance)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Place an implant back in its initializing state.
|
|
||||||
*/
|
|
||||||
def Reset() : Unit = {
|
|
||||||
Active = false
|
|
||||||
Timer = MaxTimer
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Place an implant back in its pre-initialization state.
|
|
||||||
* The implant is inactive and can not proceed to a `Ready` condition naturally from this state.
|
|
||||||
*/
|
|
||||||
def Jammed() : Unit = {
|
|
||||||
Active = false
|
|
||||||
Timer = -1
|
|
||||||
}
|
|
||||||
|
|
||||||
def Definition : ImplantDefinition = implantDef
|
|
||||||
}
|
|
||||||
|
|
||||||
object Implant {
|
|
||||||
def default : Implant = new Implant(ImplantDefinition(ImplantType.RangeMagnifier))
|
|
||||||
|
|
||||||
def apply(implantDef : ImplantDefinition) : Implant = {
|
|
||||||
new Implant(implantDef)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +1,117 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package net.psforever.objects
|
package net.psforever.objects
|
||||||
|
|
||||||
import net.psforever.objects.definition.ImplantDefinition
|
import net.psforever.objects.definition.{ImplantDefinition, Stance}
|
||||||
import net.psforever.types.ImplantType
|
import net.psforever.types.{ExoSuitType, ImplantType}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A slot "on the player" into which an implant is installed.<br>
|
* A slot "on the player" into which an implant is installed.
|
||||||
|
* In total, players have three implant slots.<br>
|
||||||
* <br>
|
* <br>
|
||||||
* In total, players have three implant slots.
|
* All implants slots start as "locked" and must be "unlocked" through battle rank advancement.
|
||||||
* At battle rank one (BR1), however, all of those slots are locked.
|
* Only after it is "unlocked" may an implant be "installed" into the slot.
|
||||||
* The player earns implants at BR16, BR12, and BR18.
|
* Upon installation, it undergoes an initialization period and then, after which, it is ready for user activation.
|
||||||
* A locked implant slot can not be used.
|
* Being jammed de-activates the implant, put it into a state of "not being ready," and causes the initialization to repeat.
|
||||||
* (The code uses "not yet unlocked" logic.)
|
|
||||||
* When unlocked, an implant may be installed into that slot.<br>
|
|
||||||
* <br>
|
|
||||||
* The default implant that the underlying slot utilizes is the "Range Magnifier."
|
|
||||||
* Until the `Installed` condition is some value other than `None`, however, the implant in the slot will not work.
|
|
||||||
*/
|
*/
|
||||||
class ImplantSlot {
|
class ImplantSlot {
|
||||||
/** is this slot available for holding an implant */
|
/** is this slot available for holding an implant */
|
||||||
private var unlocked : Boolean = false
|
private var unlocked : Boolean = false
|
||||||
|
/** whether this implant is ready for use */
|
||||||
|
private var initialized : Boolean = false
|
||||||
|
/** is this implant active */
|
||||||
|
private var active : Boolean = false
|
||||||
/** what implant is currently installed in this slot; None if there is no implant currently installed */
|
/** what implant is currently installed in this slot; None if there is no implant currently installed */
|
||||||
private var installed : Option[ImplantType.Value] = None
|
private var implant : Option[ImplantDefinition] = None
|
||||||
/** the entry for that specific implant used by the a player; always occupied by some type of implant */
|
|
||||||
private var implant : Implant = ImplantSlot.default
|
|
||||||
|
|
||||||
def Unlocked : Boolean = unlocked
|
def Unlocked : Boolean = unlocked
|
||||||
|
|
||||||
def Unlocked_=(lock : Boolean) : Boolean = {
|
def Unlocked_=(lock : Boolean) : Boolean = {
|
||||||
unlocked = lock
|
unlocked = lock || unlocked
|
||||||
Unlocked
|
Unlocked
|
||||||
}
|
}
|
||||||
|
|
||||||
def Installed : Option[ImplantType.Value] = installed
|
def Initialized : Boolean = initialized
|
||||||
|
|
||||||
def Implant : Option[Implant] = if(Installed.isDefined) { Some(implant) } else { None }
|
def Initialized_=(init : Boolean) : Boolean = {
|
||||||
|
initialized = Installed.isDefined && init
|
||||||
|
Active = Active && initialized //can not be active just yet
|
||||||
|
Initialized
|
||||||
|
}
|
||||||
|
|
||||||
def Implant_=(anImplant : Option[Implant]) : Option[Implant] = {
|
def Active : Boolean = active
|
||||||
anImplant match {
|
|
||||||
case Some(module) =>
|
def Active_=(state : Boolean) : Boolean = {
|
||||||
Implant = module
|
active = Initialized && state
|
||||||
case None =>
|
Active
|
||||||
installed = None
|
}
|
||||||
|
|
||||||
|
def Implant : ImplantType.Value = if(Installed.isDefined) {
|
||||||
|
implant.get.Type
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Active = false
|
||||||
|
Initialized = false
|
||||||
|
ImplantType.None
|
||||||
|
}
|
||||||
|
|
||||||
|
def Implant_=(anImplant : ImplantDefinition) : ImplantType.Value = {
|
||||||
|
Implant_=(Some(anImplant))
|
||||||
|
}
|
||||||
|
|
||||||
|
def Implant_=(anImplant : Option[ImplantDefinition]) : ImplantType.Value = {
|
||||||
|
if(Unlocked) {
|
||||||
|
anImplant match {
|
||||||
|
case Some(_) =>
|
||||||
|
implant = anImplant
|
||||||
|
case None =>
|
||||||
|
implant = None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Implant
|
Implant
|
||||||
}
|
}
|
||||||
|
|
||||||
def Implant_=(anImplant : Implant) : Option[Implant] = {
|
def Installed : Option[ImplantDefinition] = implant
|
||||||
implant = anImplant
|
|
||||||
installed = Some(anImplant.Definition.Type)
|
def MaxTimer : Long = Implant match {
|
||||||
Implant
|
case ImplantType.None =>
|
||||||
|
-1L
|
||||||
|
case _ =>
|
||||||
|
Installed.get.Initialization
|
||||||
|
}
|
||||||
|
|
||||||
|
def ActivationCharge : Int = {
|
||||||
|
if(Active) {
|
||||||
|
Installed.get.ActivationCharge
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the stamina consumption of the implant for any given moment of being active after its activation.
|
||||||
|
* As implant energy use can be influenced by both exo-suit worn and general stance held, both are considered.
|
||||||
|
* @param suit the exo-suit being worn
|
||||||
|
* @param stance the player's stance
|
||||||
|
* @return the amount of stamina (energy) that is consumed
|
||||||
|
*/
|
||||||
|
def Charge(suit : ExoSuitType.Value, stance : Stance.Value) : Int = {
|
||||||
|
if(Active) {
|
||||||
|
val inst = Installed.get
|
||||||
|
inst.DurationChargeBase + inst.DurationChargeByExoSuit(suit) + inst.DurationChargeByStance(stance)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def Jammed() : Unit = {
|
||||||
|
Active = false
|
||||||
|
Initialized = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object ImplantSlot {
|
object ImplantSlot {
|
||||||
private val default = new Implant(ImplantDefinition(ImplantType.RangeMagnifier))
|
|
||||||
|
|
||||||
def apply() : ImplantSlot = {
|
def apply() : ImplantSlot = {
|
||||||
new ImplantSlot()
|
new ImplantSlot()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package net.psforever.objects
|
package net.psforever.objects
|
||||||
|
|
||||||
import net.psforever.objects.definition.AvatarDefinition
|
import net.psforever.objects.definition.{AvatarDefinition, ImplantDefinition}
|
||||||
import net.psforever.objects.equipment.{Equipment, EquipmentSize}
|
import net.psforever.objects.equipment.{Equipment, EquipmentSize}
|
||||||
import net.psforever.objects.inventory.{GridInventory, InventoryItem}
|
import net.psforever.objects.inventory.{GridInventory, InventoryItem}
|
||||||
import net.psforever.packet.game.PlanetSideGUID
|
import net.psforever.packet.game.PlanetSideGUID
|
||||||
import net.psforever.types._
|
import net.psforever.types._
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
|
import scala.collection.mutable
|
||||||
|
|
||||||
class Player(private val name : String,
|
class Player(private val name : String,
|
||||||
private val faction : PlanetSideEmpire.Value,
|
private val faction : PlanetSideEmpire.Value,
|
||||||
|
|
@ -33,6 +34,9 @@ class Player(private val name : String,
|
||||||
|
|
||||||
private val loadouts : Array[Option[InfantryLoadout]] = Array.fill[Option[InfantryLoadout]](10)(None)
|
private val loadouts : Array[Option[InfantryLoadout]] = Array.fill[Option[InfantryLoadout]](10)(None)
|
||||||
|
|
||||||
|
private var bep : Long = 0
|
||||||
|
private var cep : Long = 0
|
||||||
|
private val certifications : mutable.Set[CertificationType.Value] = mutable.Set[CertificationType.Value]()
|
||||||
private val implants : Array[ImplantSlot] = Array.fill[ImplantSlot](3)(new ImplantSlot)
|
private val implants : Array[ImplantSlot] = Array.fill[ImplantSlot](3)(new ImplantSlot)
|
||||||
|
|
||||||
// private var tosRibbon : MeritCommendation.Value = MeritCommendation.None
|
// private var tosRibbon : MeritCommendation.Value = MeritCommendation.None
|
||||||
|
|
@ -312,28 +316,40 @@ class Player(private val name : String,
|
||||||
exosuit = suit
|
exosuit = suit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def BEP : Long = bep
|
||||||
|
|
||||||
|
def BEP_=(battleExperiencePoints : Long) : Long = {
|
||||||
|
bep = math.max(0L, math.min(battleExperiencePoints, 4294967295L))
|
||||||
|
BEP
|
||||||
|
}
|
||||||
|
|
||||||
|
def CEP : Long = cep
|
||||||
|
|
||||||
|
def CEP_=(commandExperiencePoints : Long) : Long = {
|
||||||
|
cep = math.max(0L, math.min(commandExperiencePoints, 4294967295L))
|
||||||
|
CEP
|
||||||
|
}
|
||||||
|
|
||||||
|
def Certifications : mutable.Set[CertificationType.Value] = certifications
|
||||||
|
|
||||||
def Implants : Array[ImplantSlot] = implants
|
def Implants : Array[ImplantSlot] = implants
|
||||||
|
|
||||||
def Implant(slot : Int) : Option[ImplantType.Value] = {
|
def Implant(slot : Int) : ImplantType.Value = {
|
||||||
if(-1 < slot && slot < implants.length) { implants(slot).Installed } else { None }
|
if(-1 < slot && slot < implants.length) { implants(slot).Implant } else { ImplantType.None }
|
||||||
}
|
}
|
||||||
|
|
||||||
def Implant(implantType : ImplantType.Value) : Option[Implant] = {
|
def InstallImplant(implant : ImplantDefinition) : Boolean = {
|
||||||
implants.find(_.Installed.contains(implantType)) match {
|
implants.find({p => p.Installed.contains(implant)}) match { //try to find the installed implant
|
||||||
case Some(slot) =>
|
|
||||||
slot.Implant
|
|
||||||
case None =>
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def InstallImplant(implant : Implant) : Boolean = {
|
|
||||||
getAvailableImplantSlot(implants.iterator, implant.Definition.Type) match {
|
|
||||||
case Some(slot) =>
|
|
||||||
slot.Implant = implant
|
|
||||||
slot.Implant.get.Reset()
|
|
||||||
true
|
|
||||||
case None =>
|
case None =>
|
||||||
|
//install in a free slot
|
||||||
|
getAvailableImplantSlot(implants.iterator, implant.Type) match {
|
||||||
|
case Some(slot) =>
|
||||||
|
slot.Implant = implant
|
||||||
|
true
|
||||||
|
case None =>
|
||||||
|
false
|
||||||
|
}
|
||||||
|
case Some(_) =>
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -344,7 +360,7 @@ class Player(private val name : String,
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
val slot = iter.next
|
val slot = iter.next
|
||||||
if(!slot.Unlocked || slot.Installed.contains(implantType)) {
|
if(!slot.Unlocked || slot.Implant == implantType) {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
else if(slot.Installed.isEmpty) {
|
else if(slot.Installed.isEmpty) {
|
||||||
|
|
@ -357,7 +373,7 @@ class Player(private val name : String,
|
||||||
}
|
}
|
||||||
|
|
||||||
def UninstallImplant(implantType : ImplantType.Value) : Boolean = {
|
def UninstallImplant(implantType : ImplantType.Value) : Boolean = {
|
||||||
implants.find({slot => slot.Installed.contains(implantType)}) match {
|
implants.find({slot => slot.Implant == implantType}) match {
|
||||||
case Some(slot) =>
|
case Some(slot) =>
|
||||||
slot.Implant = None
|
slot.Implant = None
|
||||||
true
|
true
|
||||||
|
|
@ -368,9 +384,9 @@ class Player(private val name : String,
|
||||||
|
|
||||||
def ResetAllImplants() : Unit = {
|
def ResetAllImplants() : Unit = {
|
||||||
implants.foreach(slot => {
|
implants.foreach(slot => {
|
||||||
slot.Implant match {
|
slot.Installed match {
|
||||||
case Some(implant) =>
|
case Some(_) =>
|
||||||
implant.Reset()
|
slot.Initialized = false
|
||||||
case None => ;
|
case None => ;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -570,7 +586,7 @@ object Player {
|
||||||
//hand over implants
|
//hand over implants
|
||||||
(0 until 3).foreach(index => {
|
(0 until 3).foreach(index => {
|
||||||
if(obj.Implants(index).Unlocked = player.Implants(index).Unlocked) {
|
if(obj.Implants(index).Unlocked = player.Implants(index).Unlocked) {
|
||||||
obj.Implants(index).Implant = player.Implants(index).Implant
|
obj.Implants(index).Implant = player.Implants(index).Installed
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
//hand over knife
|
//hand over knife
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,13 @@ import scala.collection.mutable
|
||||||
* An `Enumeration` of a variety of poses or generalized movement.
|
* An `Enumeration` of a variety of poses or generalized movement.
|
||||||
*/
|
*/
|
||||||
object Stance extends Enumeration {
|
object Stance extends Enumeration {
|
||||||
val Crouching,
|
val
|
||||||
Standing,
|
Crouching,
|
||||||
Walking, //not used, but should still be defined
|
CrouchWalking, //not used, but should still be defined
|
||||||
Running = Value
|
Standing,
|
||||||
|
Walking, //not used, but should still be defined
|
||||||
|
Running
|
||||||
|
= Value
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package net.psforever.objects.definition.converter
|
package net.psforever.objects.definition.converter
|
||||||
|
|
||||||
import net.psforever.objects.{EquipmentSlot, Player}
|
import net.psforever.objects.{EquipmentSlot, GlobalDefinitions, ImplantSlot, Player}
|
||||||
import net.psforever.objects.equipment.Equipment
|
import net.psforever.objects.equipment.Equipment
|
||||||
import net.psforever.packet.game.PlanetSideGUID
|
import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, DetailedCharacterData, DrawnSlot, ImplantEffects, ImplantEntry, InternalSlot, InventoryData, PlacementData, RibbonBars, UniformStyle}
|
||||||
import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, DetailedCharacterData, DrawnSlot, InternalSlot, InventoryData, PlacementData, RibbonBars, UniformStyle}
|
import net.psforever.types.{GrenadeState, ImplantType}
|
||||||
import net.psforever.types.GrenadeState
|
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
import scala.util.{Success, Try}
|
import scala.util.{Success, Try}
|
||||||
|
|
@ -17,11 +16,11 @@ class AvatarConverter extends ObjectCreateConverter[Player]() {
|
||||||
MakeAppearanceData(obj),
|
MakeAppearanceData(obj),
|
||||||
obj.Health / obj.MaxHealth * 255, //TODO not precise
|
obj.Health / obj.MaxHealth * 255, //TODO not precise
|
||||||
obj.Armor / obj.MaxArmor * 255, //TODO not precise
|
obj.Armor / obj.MaxArmor * 255, //TODO not precise
|
||||||
UniformStyle.Normal,
|
DressBattleRank(obj),
|
||||||
0,
|
DressCommandRank(obj),
|
||||||
|
recursiveMakeImplantEffects(obj.Implants.iterator),
|
||||||
None, //TODO cosmetics
|
None, //TODO cosmetics
|
||||||
None, //TODO implant effects
|
InventoryData(MakeHolsters(obj, BuildEquipment).sortBy(_.parentSlot)), //TODO is sorting necessary?
|
||||||
InventoryData(MakeHolsters(obj, BuildEquipment).sortBy(_.parentSlot)),
|
|
||||||
GetDrawnSlot(obj)
|
GetDrawnSlot(obj)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -32,20 +31,21 @@ class AvatarConverter extends ObjectCreateConverter[Player]() {
|
||||||
Success(
|
Success(
|
||||||
DetailedCharacterData(
|
DetailedCharacterData(
|
||||||
MakeAppearanceData(obj),
|
MakeAppearanceData(obj),
|
||||||
|
obj.BEP,
|
||||||
|
obj.CEP,
|
||||||
obj.MaxHealth,
|
obj.MaxHealth,
|
||||||
obj.Health,
|
obj.Health,
|
||||||
obj.Armor,
|
obj.Armor,
|
||||||
1, 7, 7,
|
|
||||||
obj.MaxStamina,
|
obj.MaxStamina,
|
||||||
obj.Stamina,
|
obj.Stamina,
|
||||||
28, 4, 44, 84, 104, 1900,
|
obj.Certifications.toList.sortBy(_.id), //TODO is sorting necessary?
|
||||||
|
MakeImplantEntries(obj),
|
||||||
List.empty[String], //TODO fte list
|
List.empty[String], //TODO fte list
|
||||||
List.empty[String], //TODO tutorial list
|
List.empty[String], //TODO tutorial list
|
||||||
InventoryData((MakeHolsters(obj, BuildDetailedEquipment) ++ MakeFifthSlot(obj) ++ MakeInventory(obj)).sortBy(_.parentSlot)),
|
InventoryData((MakeHolsters(obj, BuildDetailedEquipment) ++ MakeFifthSlot(obj) ++ MakeInventory(obj)).sortBy(_.parentSlot)),
|
||||||
GetDrawnSlot(obj)
|
GetDrawnSlot(obj)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
//TODO tidy this mess up
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -64,8 +64,8 @@ class AvatarConverter extends ObjectCreateConverter[Player]() {
|
||||||
"",
|
"",
|
||||||
0,
|
0,
|
||||||
obj.isBackpack,
|
obj.isBackpack,
|
||||||
obj.Orientation.y.toInt,
|
obj.Orientation.y,
|
||||||
obj.FacingYawUpper.toInt,
|
obj.FacingYawUpper,
|
||||||
true,
|
true,
|
||||||
GrenadeState.None,
|
GrenadeState.None,
|
||||||
false,
|
false,
|
||||||
|
|
@ -75,6 +75,107 @@ class AvatarConverter extends ObjectCreateConverter[Player]() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select the appropriate `UniformStyle` design for a player's accumulated battle experience points.
|
||||||
|
* At certain battle ranks, all exo-suits undergo some form of coloration change.
|
||||||
|
* @param obj the `Player` game object
|
||||||
|
* @return the resulting uniform upgrade level
|
||||||
|
*/
|
||||||
|
private def DressBattleRank(obj : Player) : UniformStyle.Value = {
|
||||||
|
val bep : Long = obj.BEP
|
||||||
|
if(bep > 2583440) { //BR25+
|
||||||
|
UniformStyle.ThirdUpgrade
|
||||||
|
}
|
||||||
|
else if(bep > 308989) { //BR14+
|
||||||
|
UniformStyle.SecondUpgrade
|
||||||
|
}
|
||||||
|
else if(bep > 44999) { //BR7+
|
||||||
|
UniformStyle.FirstUpgrade
|
||||||
|
}
|
||||||
|
else { //BR1+
|
||||||
|
UniformStyle.Normal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select the appropriate design for a player's accumulated command experience points.
|
||||||
|
* Visual cues for command rank include armlets, anklets, and, finally, a backpack, awarded at different ranks.
|
||||||
|
* @param obj the `Player` game object
|
||||||
|
* @return the resulting uniform upgrade level
|
||||||
|
*/
|
||||||
|
private def DressCommandRank(obj : Player) : Int = {
|
||||||
|
val cep = obj.CEP
|
||||||
|
if(cep > 599999) {
|
||||||
|
5
|
||||||
|
}
|
||||||
|
else if(cep > 299999) {
|
||||||
|
4
|
||||||
|
}
|
||||||
|
else if(cep > 149999) {
|
||||||
|
3
|
||||||
|
}
|
||||||
|
else if(cep > 49999) {
|
||||||
|
2
|
||||||
|
}
|
||||||
|
else if(cep > 9999) {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform an `Array` of `Implant` objects into a `List` of `ImplantEntry` objects suitable as packet data.
|
||||||
|
* @param obj the `Player` game object
|
||||||
|
* @return the resulting implant `List`
|
||||||
|
* @see `ImplantEntry` in `DetailedCharacterData`
|
||||||
|
*/
|
||||||
|
private def MakeImplantEntries(obj : Player) : List[ImplantEntry] = {
|
||||||
|
obj.Implants.map(slot => {
|
||||||
|
slot.Installed match {
|
||||||
|
case Some(_) =>
|
||||||
|
if(slot.Initialized) {
|
||||||
|
ImplantEntry(slot.Implant, None)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ImplantEntry(slot.Implant, Some(slot.Installed.get.Initialization.toInt))
|
||||||
|
}
|
||||||
|
case None =>
|
||||||
|
ImplantEntry(ImplantType.None, None)
|
||||||
|
}
|
||||||
|
}).toList
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find an active implant whose effect will be displayed on this player.
|
||||||
|
* @param iter an `Iterator` of `ImplantSlot` objects
|
||||||
|
* @return the effect of an active implant
|
||||||
|
*/
|
||||||
|
@tailrec private def recursiveMakeImplantEffects(iter : Iterator[ImplantSlot]) : Option[ImplantEffects.Value] = {
|
||||||
|
if(!iter.hasNext) {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val slot = iter.next
|
||||||
|
if(slot.Active) {
|
||||||
|
import GlobalDefinitions._
|
||||||
|
slot.Installed match {
|
||||||
|
case Some(`advanced_regen`) =>
|
||||||
|
Some(ImplantEffects.RegenEffects)
|
||||||
|
case Some(`darklight_vision`) =>
|
||||||
|
Some(ImplantEffects.DarklightEffects)
|
||||||
|
case Some(`personal_shield`) =>
|
||||||
|
Some(ImplantEffects.PersonalShieldEffects)
|
||||||
|
case Some(`surge`) =>
|
||||||
|
Some(ImplantEffects.SurgeEffects)
|
||||||
|
case _ => ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
recursiveMakeImplantEffects(iter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a player with an inventory, convert the contents of that inventory into converted-decoded packet data.
|
* Given a player with an inventory, convert the contents of that inventory into converted-decoded packet data.
|
||||||
* The inventory is not represented in a `0x17` `Player`, so the conversion is only valid for `0x18` avatars.
|
* The inventory is not represented in a `0x17` `Player`, so the conversion is only valid for `0x18` avatars.
|
||||||
|
|
@ -139,6 +240,14 @@ class AvatarConverter extends ObjectCreateConverter[Player]() {
|
||||||
InternalSlot(equip.Definition.ObjectId, equip.GUID, index, equip.Definition.Packet.DetailedConstructorData(equip).get)
|
InternalSlot(equip.Definition.ObjectId, equip.GUID, index, equip.Definition.Packet.DetailedConstructorData(equip).get)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given some equipment holsters, convert the contents of those holsters into converted-decoded packet data.
|
||||||
|
* @param iter an `Iterator` of `EquipmentSlot` objects that are a part of the player's holsters
|
||||||
|
* @param builder the function used to transform to the decoded packet form
|
||||||
|
* @param list the current `List` of transformed data
|
||||||
|
* @param index which holster is currently being explored
|
||||||
|
* @return the `List` of inventory data created from the holsters
|
||||||
|
*/
|
||||||
@tailrec private def recursiveMakeHolsters(iter : Iterator[EquipmentSlot], builder : ((Int, Equipment) => InternalSlot), list : List[InternalSlot] = Nil, index : Int = 0) : List[InternalSlot] = {
|
@tailrec private def recursiveMakeHolsters(iter : Iterator[EquipmentSlot], builder : ((Int, Equipment) => InternalSlot), list : List[InternalSlot] = Nil, index : Int = 0) : List[InternalSlot] = {
|
||||||
if(!iter.hasNext) {
|
if(!iter.hasNext) {
|
||||||
list
|
list
|
||||||
|
|
|
||||||
|
|
@ -117,12 +117,27 @@ final case class CharacterAppearanceData(pos : PlacementData,
|
||||||
val placementSize : Long = pos.bitsize
|
val placementSize : Long = pos.bitsize
|
||||||
val nameStringSize : Long = StreamBitSize.stringBitSize(basic_appearance.name, 16) + CharacterAppearanceData.namePadding(pos.vel)
|
val nameStringSize : Long = StreamBitSize.stringBitSize(basic_appearance.name, 16) + CharacterAppearanceData.namePadding(pos.vel)
|
||||||
val outfitStringSize : Long = StreamBitSize.stringBitSize(outfit_name, 16) + CharacterAppearanceData.outfitNamePadding
|
val outfitStringSize : Long = StreamBitSize.stringBitSize(outfit_name, 16) + CharacterAppearanceData.outfitNamePadding
|
||||||
val altModelSize = if(on_zipline || backpack) { 1L } else { 0L }
|
val altModelSize = CharacterAppearanceData.altModelBit(this).getOrElse(0)
|
||||||
335L + placementSize + nameStringSize + outfitStringSize + altModelSize
|
335L + placementSize + nameStringSize + outfitStringSize + altModelSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
|
object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
|
||||||
|
/**
|
||||||
|
* When a player is released-dead or attached to a zipline, their basic infantry model is replaced with a different one.
|
||||||
|
* In the former casde, a backpack.
|
||||||
|
* In the latter case, a ball of colored energy.
|
||||||
|
* In this state, the length of the stream of data is modified.
|
||||||
|
* @param app the appearance
|
||||||
|
* @return the length of the variable field that exists when using alternate models
|
||||||
|
*/
|
||||||
|
def altModelBit(app : CharacterAppearanceData) : Option[Int] = if(app.backpack || app.on_zipline) {
|
||||||
|
Some(1)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the padding of the player's name.
|
* Get the padding of the player's name.
|
||||||
* The padding will always be a number 0-7.
|
* The padding will always be a number 0-7.
|
||||||
|
|
@ -151,7 +166,7 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
|
||||||
("faction" | PlanetSideEmpire.codec) ::
|
("faction" | PlanetSideEmpire.codec) ::
|
||||||
("black_ops" | bool) ::
|
("black_ops" | bool) ::
|
||||||
(("alt_model" | bool) >>:~ { alt_model => //modifies stream format (to display alternate player models)
|
(("alt_model" | bool) >>:~ { alt_model => //modifies stream format (to display alternate player models)
|
||||||
ignore(1) :: //unknown
|
ignore(1) :: //unknown
|
||||||
("jammered" | bool) ::
|
("jammered" | bool) ::
|
||||||
bool :: //crashes client
|
bool :: //crashes client
|
||||||
uint(16) :: //unknown, but usually 0
|
uint(16) :: //unknown, but usually 0
|
||||||
|
|
@ -204,7 +219,7 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
|
||||||
Attempt.failure(Err(s"character $name's faction can not declare as neutral"))
|
Attempt.failure(Err(s"character $name's faction can not declare as neutral"))
|
||||||
|
|
||||||
case CharacterAppearanceData(pos, BasicCharacterData(name, faction, sex, head, v1), v2, bops, jamd, suit, outfit, logo, bpack, facingPitch, facingYawUpper, lfs, gstate, cloaking, charging, zipline, ribbons) =>
|
case CharacterAppearanceData(pos, BasicCharacterData(name, faction, sex, head, v1), v2, bops, jamd, suit, outfit, logo, bpack, facingPitch, facingYawUpper, lfs, gstate, cloaking, charging, zipline, ribbons) =>
|
||||||
val has_outfit_name : Long = outfit.length.toLong //todo this is a kludge
|
val has_outfit_name : Long = outfit.length.toLong //TODO this is a kludge
|
||||||
var alt_model : Boolean = false
|
var alt_model : Boolean = false
|
||||||
var alt_model_extrabit : Option[Boolean] = None
|
var alt_model_extrabit : Option[Boolean] = None
|
||||||
if(zipline || bpack) {
|
if(zipline || bpack) {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,31 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package net.psforever.packet.game.objectcreate
|
package net.psforever.packet.game.objectcreate
|
||||||
|
|
||||||
|
import net.psforever.newcodecs.newcodecs
|
||||||
import net.psforever.packet.{Marshallable, PacketHelpers}
|
import net.psforever.packet.{Marshallable, PacketHelpers}
|
||||||
|
import net.psforever.types.{CertificationType, ImplantType}
|
||||||
import scodec.{Attempt, Codec}
|
import scodec.{Attempt, Codec}
|
||||||
import scodec.codecs._
|
import scodec.codecs._
|
||||||
import shapeless.{::, HNil}
|
import shapeless.{::, HNil}
|
||||||
|
|
||||||
|
import scala.annotation.tailrec
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An entry in the `List` of valid implant slots in `DetailedCharacterData`.
|
||||||
|
* `activation`, if defined, indicates the time remaining (in seconds?) before an implant becomes usable.
|
||||||
|
* @param implant the type of implant
|
||||||
|
* @param activation the activation timer;
|
||||||
|
* technically, this is "unconfirmed"
|
||||||
|
* @see `ImplantType`
|
||||||
|
*/
|
||||||
|
final case class ImplantEntry(implant : ImplantType.Value,
|
||||||
|
activation : Option[Int]) extends StreamBitSize {
|
||||||
|
override def bitsize : Long = {
|
||||||
|
val activationSize = if(activation.isDefined) { 12L } else { 5L }
|
||||||
|
5L + activationSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A representation of the avatar portion of `ObjectCreateDetailedMessage` packet data.
|
* A representation of the avatar portion of `ObjectCreateDetailedMessage` packet data.
|
||||||
* This densely-packed information outlines most of the specifics required to depict a character as an avatar.<br>
|
* This densely-packed information outlines most of the specifics required to depict a character as an avatar.<br>
|
||||||
|
|
@ -15,16 +35,16 @@ import shapeless.{::, HNil}
|
||||||
* <br>
|
* <br>
|
||||||
* Divisions exist to make the data more manageable.
|
* Divisions exist to make the data more manageable.
|
||||||
* The first division of data only manages the general appearance of the player's in-game model.
|
* The first division of data only manages the general appearance of the player's in-game model.
|
||||||
* The second division (currently, the fields actually in this class) manages the status of the character as an avatar.
|
* It is shared between `DetailedCharacterData` avatars and `CharacterData` player characters.
|
||||||
|
* The second division (of fields) manages the status of the character as an avatar.
|
||||||
* In general, it passes more thorough data about the character that the client can display to the owner of the client.
|
* In general, it passes more thorough data about the character that the client can display to the owner of the client.
|
||||||
* For example, health is a full number, rather than a percentage.
|
* For example, health is a full number, rather than a percentage.
|
||||||
* Just as prominent is the list of first time events and the list of completed tutorials.
|
* Just as prominent is the list of first time events and the list of completed tutorials.
|
||||||
* The third subdivision is also exclusive to avatar-prepared characters and contains (omitted).
|
* The third subdivision is also exclusive to avatar-prepared characters and contains (omitted).
|
||||||
* The fourth is the inventory (composed of `Direct`-type objects).<br>
|
* The fourth is the inventory (composed of `Direct`-type objects).
|
||||||
* <br>
|
|
||||||
* Exploration:<br>
|
|
||||||
* Lots of analysis needed for the remainder of the byte data.
|
|
||||||
* @param appearance data about the avatar's basic aesthetics
|
* @param appearance data about the avatar's basic aesthetics
|
||||||
|
* @param bep the avatar's battle experience points, which determines his Battle Rank
|
||||||
|
* @param cep the avatar's command experience points, which determines his Command Rank
|
||||||
* @param healthMax for `x / y` of hitpoints, this is the avatar's `y` value;
|
* @param healthMax for `x / y` of hitpoints, this is the avatar's `y` value;
|
||||||
* range is 0-65535
|
* range is 0-65535
|
||||||
* @param health for `x / y` of hitpoints, this is the avatar's `x` value;
|
* @param health for `x / y` of hitpoints, this is the avatar's `x` value;
|
||||||
|
|
@ -42,32 +62,25 @@ import shapeless.{::, HNil}
|
||||||
* range is 0-65535
|
* range is 0-65535
|
||||||
* @param stamina for `x / y` of stamina points, this is the avatar's `x` value;
|
* @param stamina for `x / y` of stamina points, this is the avatar's `x` value;
|
||||||
* range is 0-65535
|
* range is 0-65535
|
||||||
* @param unk4 na;
|
* @param certs the `List` of active certifications
|
||||||
* defaults to 28
|
* @param implants the `List` of implant slots currently possessed by this avatar
|
||||||
* @param unk5 na;
|
|
||||||
* defaults to 4
|
|
||||||
* @param unk6 na;
|
|
||||||
* defaults to 44
|
|
||||||
* @param unk7 na;
|
|
||||||
* defaults to 84
|
|
||||||
* @param unk8 na;
|
|
||||||
* defaults to 104
|
|
||||||
* @param unk9 na;
|
|
||||||
* defaults to 1900
|
|
||||||
* @param firstTimeEvents the list of first time events performed by this avatar;
|
* @param firstTimeEvents the list of first time events performed by this avatar;
|
||||||
* the size field is a 32-bit number;
|
* the size field is a 32-bit number;
|
||||||
* the first entry may be padded
|
* the first entry may be padded
|
||||||
* @param tutorials the list of tutorials completed by this avatar;
|
* @param tutorials the `List` of tutorials completed by this avatar;
|
||||||
* the size field is a 32-bit number;
|
* the size field is a 32-bit number;
|
||||||
* the first entry may be padded
|
* the first entry may be padded
|
||||||
* @param inventory the avatar's inventory
|
* @param inventory the avatar's inventory
|
||||||
* @param drawn_slot the holster that is initially drawn
|
* @param drawn_slot the holster that is initially drawn
|
||||||
* @see `CharacterAppearanceData`
|
* @see `CharacterAppearanceData`<br>
|
||||||
* @see `CharacterData`
|
* `CharacterData`<br>
|
||||||
* @see `InventoryData`
|
* `CertificationType`<br>
|
||||||
* @see `DrawnSlot`
|
* `InventoryData`<br>
|
||||||
|
* `DrawnSlot`
|
||||||
*/
|
*/
|
||||||
final case class DetailedCharacterData(appearance : CharacterAppearanceData,
|
final case class DetailedCharacterData(appearance : CharacterAppearanceData,
|
||||||
|
bep : Long,
|
||||||
|
cep : Long,
|
||||||
healthMax : Int,
|
healthMax : Int,
|
||||||
health : Int,
|
health : Int,
|
||||||
armor : Int,
|
armor : Int,
|
||||||
|
|
@ -76,177 +89,252 @@ final case class DetailedCharacterData(appearance : CharacterAppearanceData,
|
||||||
unk3 : Int, //7
|
unk3 : Int, //7
|
||||||
staminaMax : Int,
|
staminaMax : Int,
|
||||||
stamina : Int,
|
stamina : Int,
|
||||||
unk4 : Int, //28
|
certs : List[CertificationType.Value],
|
||||||
unk5 : Int, //4
|
implants : List[ImplantEntry],
|
||||||
unk6 : Int, //44
|
|
||||||
unk7 : Int, //84
|
|
||||||
unk8 : Int, //104
|
|
||||||
unk9 : Int, //1900
|
|
||||||
firstTimeEvents : List[String],
|
firstTimeEvents : List[String],
|
||||||
tutorials : List[String],
|
tutorials : List[String],
|
||||||
inventory : Option[InventoryData],
|
inventory : Option[InventoryData],
|
||||||
drawn_slot : DrawnSlot.Value = DrawnSlot.None
|
drawn_slot : DrawnSlot.Value = DrawnSlot.None
|
||||||
) extends ConstructorData {
|
) extends ConstructorData {
|
||||||
|
|
||||||
override def bitsize : Long = {
|
override def bitsize : Long = {
|
||||||
//factor guard bool values into the base size, not its corresponding optional field
|
//factor guard bool values into the base size, not corresponding optional fields, unless contained or enumerated
|
||||||
val appearanceSize = appearance.bitsize
|
val appearanceSize = appearance.bitsize
|
||||||
|
val certSize = (certs.length + 1) * 8 //cert list
|
||||||
|
var implantSize : Long = 0L //implant list
|
||||||
|
for(entry <- implants) {
|
||||||
|
implantSize += entry.bitsize
|
||||||
|
}
|
||||||
|
val implantPadding = DetailedCharacterData.implantFieldPadding(implants, CharacterAppearanceData.altModelBit(appearance))
|
||||||
val fteLen = firstTimeEvents.size //fte list
|
val fteLen = firstTimeEvents.size //fte list
|
||||||
var eventListSize : Long = 32L + DetailedCharacterData.ftePadding(fteLen)
|
var eventListSize : Long = 32L + DetailedCharacterData.ftePadding(fteLen, implantPadding)
|
||||||
for(str <- firstTimeEvents) {
|
for(str <- firstTimeEvents) {
|
||||||
eventListSize += StreamBitSize.stringBitSize(str)
|
eventListSize += StreamBitSize.stringBitSize(str)
|
||||||
}
|
}
|
||||||
val tutLen = tutorials.size //tutorial list
|
val tutLen = tutorials.size //tutorial list
|
||||||
var tutorialListSize : Long = 32L + DetailedCharacterData.tutPadding(fteLen, tutLen)
|
var tutorialListSize : Long = 32L + DetailedCharacterData.tutPadding(fteLen, tutLen, implantPadding)
|
||||||
for(str <- tutorials) {
|
for(str <- tutorials) {
|
||||||
tutorialListSize += StreamBitSize.stringBitSize(str)
|
tutorialListSize += StreamBitSize.stringBitSize(str)
|
||||||
}
|
}
|
||||||
var inventorySize : Long = 0L //inventory
|
val inventorySize : Long = if(inventory.isDefined) { //inventory
|
||||||
if(inventory.isDefined) {
|
inventory.get.bitsize
|
||||||
inventorySize = inventory.get.bitsize
|
|
||||||
}
|
}
|
||||||
713L + appearanceSize + eventListSize + tutorialListSize + inventorySize
|
else {
|
||||||
|
0L
|
||||||
|
}
|
||||||
|
649L + appearanceSize + certSize + implantSize + eventListSize + tutorialListSize + inventorySize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
|
object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
|
||||||
/**
|
/**
|
||||||
* Overloaded constructor for `DetailedCharacterData` that skips all the unknowns by assigning defaulted values.
|
* Overloaded constructor for `DetailedCharacterData` that requires an inventory and drops unknown values.
|
||||||
* It also allows for a not-optional inventory.
|
|
||||||
* @param appearance data about the avatar's basic aesthetics
|
* @param appearance data about the avatar's basic aesthetics
|
||||||
|
* @param bep the avatar's battle experience points, which determines his Battle Rank
|
||||||
|
* @param cep the avatar's command experience points, which determines his Command Rank
|
||||||
* @param healthMax for `x / y` of hitpoints, this is the avatar's `y` value
|
* @param healthMax for `x / y` of hitpoints, this is the avatar's `y` value
|
||||||
* @param health for `x / y` of hitpoints, this is the avatar's `x` value
|
* @param health for `x / y` of hitpoints, this is the avatar's `x` value
|
||||||
* @param armor for `x / y` of armor points, this is the avatar's `x` value
|
* @param armor for `x / y` of armor points, this is the avatar's `x` value
|
||||||
* @param staminaMax for `x / y` of stamina points, this is the avatar's `y` value
|
* @param staminaMax for `x / y` of stamina points, this is the avatar's `y` value
|
||||||
* @param stamina for `x / y` of stamina points, this is the avatar's `x` value
|
* @param stamina for `x / y` of stamina points, this is the avatar's `x` value
|
||||||
* @param firstTimeEvents the list of first time events performed by this avatar
|
* @param certs the `List` of active certifications
|
||||||
* @param tutorials the list of tutorials completed by this avatar
|
* @param implants the `List` of implant slots currently possessed by this avatar
|
||||||
* @param inventory the avatar's inventory
|
|
||||||
* @param drawn_slot the holster that is initially drawn
|
|
||||||
* @return a `DetailedCharacterData` object
|
|
||||||
*/
|
|
||||||
def apply(appearance : CharacterAppearanceData, healthMax : Int, health : Int, armor : Int, staminaMax : Int, stamina : Int, firstTimeEvents : List[String], tutorials : List[String], inventory : InventoryData, drawn_slot : DrawnSlot.Value) : DetailedCharacterData =
|
|
||||||
new DetailedCharacterData(appearance, healthMax, health, armor, 1, 7, 7, staminaMax, stamina, 28, 4, 44, 84, 104, 1900, firstTimeEvents, tutorials, Some(inventory), drawn_slot)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overloaded constructor for `DetailedCharacterData` that allows for a not-optional inventory.
|
|
||||||
* @param appearance data about the avatar's basic aesthetics
|
|
||||||
* @param healthMax for `x / y` of hitpoints, this is the avatar's `y` value
|
|
||||||
* @param health for `x / y` of hitpoints, this is the avatar's `x` value
|
|
||||||
* @param armor for `x / y` of armor points, this is the avatar's `x` value
|
|
||||||
* @param unk1 na
|
|
||||||
* @param unk2 na
|
|
||||||
* @param unk3 na
|
|
||||||
* @param staminaMax for `x / y` of stamina points, this is the avatar's `y` value
|
|
||||||
* @param stamina for `x / y` of stamina points, this is the avatar's `x` value
|
|
||||||
* @param unk4 na
|
|
||||||
* @param unk5 na
|
|
||||||
* @param unk6 na
|
|
||||||
* @param unk7 na
|
|
||||||
* @param unk8 na
|
|
||||||
* @param unk9 na
|
|
||||||
* @param firstTimeEvents the list of first time events performed by this avatar
|
* @param firstTimeEvents the list of first time events performed by this avatar
|
||||||
* @param tutorials the list of tutorials completed by this avatar
|
* @param tutorials the list of tutorials completed by this avatar
|
||||||
* @param inventory the avatar's inventory
|
* @param inventory the avatar's inventory
|
||||||
* @param drawn_slot the holster that is initially drawn
|
* @param drawn_slot the holster that is initially drawn
|
||||||
* @return a `DetailedCharacterData` object
|
* @return a `DetailedCharacterData` object
|
||||||
*/
|
*/
|
||||||
def apply(appearance : CharacterAppearanceData, healthMax : Int, health : Int, armor : Int, unk1 : Int, unk2 : Int, unk3 : Int, staminaMax : Int, stamina : Int, unk4 : Int, unk5 : Int, unk6 : Int, unk7 : Int, unk8 : Int, unk9 : Int, firstTimeEvents : List[String], tutorials : List[String], inventory : InventoryData, drawn_slot : DrawnSlot.Value) : DetailedCharacterData =
|
def apply(appearance : CharacterAppearanceData, bep : Long, cep : Long, healthMax : Int, health : Int, armor : Int, staminaMax : Int, stamina : Int, certs : List[CertificationType.Value], implants : List[ImplantEntry], firstTimeEvents : List[String], tutorials : List[String], inventory : InventoryData, drawn_slot : DrawnSlot.Value) : DetailedCharacterData =
|
||||||
new DetailedCharacterData(appearance, healthMax, health, armor, unk1, unk2, unk3, staminaMax, stamina, unk4, unk5, unk6, unk7, unk8, unk9, firstTimeEvents, tutorials, Some(inventory), drawn_slot)
|
new DetailedCharacterData(appearance, bep, cep, healthMax, health, armor, 1, 7, 7, staminaMax, stamina, certs, implants, firstTimeEvents, tutorials, Some(inventory), drawn_slot)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `Codec` for entries in the `List` of implants.
|
||||||
|
*/
|
||||||
|
private val implant_entry_codec : Codec[ImplantEntry] = (
|
||||||
|
("implant" | ImplantType.codec) ::
|
||||||
|
(bool >>:~ { guard =>
|
||||||
|
newcodecs.binary_choice(guard, uintL(5), uintL(12)).hlist
|
||||||
|
})
|
||||||
|
).xmap[ImplantEntry] (
|
||||||
|
{
|
||||||
|
case implant :: true :: _ :: HNil =>
|
||||||
|
ImplantEntry(implant, None)
|
||||||
|
|
||||||
|
case implant :: false :: extra :: HNil =>
|
||||||
|
ImplantEntry(implant, Some(extra))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case ImplantEntry(implant, None) =>
|
||||||
|
implant :: true :: 0 :: HNil
|
||||||
|
|
||||||
|
case ImplantEntry(implant, Some(extra)) =>
|
||||||
|
implant :: false :: extra :: HNil
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A player's battle rank, determined by their battle experience points, determines how many implants to which they have access.
|
||||||
|
* Starting with "no implants" at BR1, a player earns one at each of the three ranks: BR6, BR12, and BR18.
|
||||||
|
* @param bep battle experience points
|
||||||
|
* @return the number of accessible implant slots
|
||||||
|
*/
|
||||||
|
private def numberOfImplantSlots(bep : Long) : Int = {
|
||||||
|
if(bep > 754370) { //BR18+
|
||||||
|
3
|
||||||
|
}
|
||||||
|
else if(bep > 197753) { //BR12+
|
||||||
|
2
|
||||||
|
}
|
||||||
|
else if(bep > 29999) { //BR6+
|
||||||
|
1
|
||||||
|
}
|
||||||
|
else { //BR1+
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The padding value of the first entry in either of two byte-aligned `List` structures.
|
||||||
|
* @param implants implant entries
|
||||||
|
* @return the pad length in bits `0 <= n < 8`
|
||||||
|
*/
|
||||||
|
private def implantFieldPadding(implants : List[ImplantEntry], varBit : Option[Int] = None) : Int = {
|
||||||
|
val base : Int = 5 //the offset with no implant entries
|
||||||
|
val baseOffset : Int = base - varBit.getOrElse(0)
|
||||||
|
val resultA = if(baseOffset < 0) { 8 - baseOffset } else { baseOffset % 8 }
|
||||||
|
|
||||||
|
var implantOffset : Int = 0
|
||||||
|
implants.foreach({entry =>
|
||||||
|
implantOffset += entry.bitsize.toInt
|
||||||
|
})
|
||||||
|
val resultB : Int = resultA - (implantOffset % 8)
|
||||||
|
if(resultB < 0) { 8 - resultB } else { resultB }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Players with certain battle rank will always have a certain number of implant slots.
|
||||||
|
* The encoding requires it.
|
||||||
|
* Pad empty slots onto the end of a list of
|
||||||
|
* @param size the required number of implant slots
|
||||||
|
* @param list the `List` of implant slots
|
||||||
|
* @return a fully-populated (or over-populated) `List` of implant slots
|
||||||
|
* @see `ImplantEntry`
|
||||||
|
*/
|
||||||
|
@tailrec private def recursiveEnsureImplantSlots(size : Int, list : List[ImplantEntry] = Nil) : List[ImplantEntry] = {
|
||||||
|
if(list.length >= size) {
|
||||||
|
list
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
recursiveEnsureImplantSlots(size, list :+ ImplantEntry(ImplantType.None, None))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the padding of the first entry in the first time events list.
|
* Get the padding of the first entry in the first time events list.
|
||||||
* The padding will always be a number 0-7.
|
* @param len the length of the first time events list
|
||||||
* @param len the length of the list
|
* @param implantPadding the padding that resulted from implant entries
|
||||||
* @return the pad length in bits
|
* @return the pad length in bits `0 <= n < 8`
|
||||||
*/
|
*/
|
||||||
private def ftePadding(len : Long) : Int = {
|
private def ftePadding(len : Long, implantPadding : Int) : Int = {
|
||||||
//TODO the parameters for this function are not correct
|
|
||||||
//TODO the proper padding length should reflect all variability in the stream prior to this point
|
//TODO the proper padding length should reflect all variability in the stream prior to this point
|
||||||
if(len > 0) {
|
if(len > 0) {
|
||||||
5
|
implantPadding
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
0
|
0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the padding of the first entry in the completed tutorials list.
|
* Get the padding of the first entry in the completed tutorials list.<br>
|
||||||
* The padding will always be a number 0-7.<br>
|
|
||||||
* <br>
|
* <br>
|
||||||
* The tutorials list follows the first time event list and that contains byte-aligned strings too.
|
* The tutorials list follows the first time event list and also contains byte-aligned strings.
|
||||||
* While there will be more to the padding, this other list is important.
|
* If the both lists are populated or empty at the same time, the first entry will not need padding.
|
||||||
* Any elements in that list causes the automatic byte-alignment of this list's first entry.
|
* If the first time events list is unpopulated, but this list is populated, the first entry will need padding bits.
|
||||||
* @param len the length of the list
|
* @param len the length of the first time events list
|
||||||
* @return the pad length in bits
|
* @param len2 the length of the tutorial list
|
||||||
|
* @param implantPadding the padding that resulted from implant entries
|
||||||
|
* @return the pad length in bits `n < 8`
|
||||||
*/
|
*/
|
||||||
private def tutPadding(len : Long, len2 : Long) : Int = {
|
private def tutPadding(len : Long, len2 : Long, implantPadding : Int) : Int = {
|
||||||
if(len > 0) //automatic alignment from previous List
|
if(len > 0) {
|
||||||
0
|
0 //automatic alignment from previous List
|
||||||
else if(len2 > 0) //need to align for elements
|
}
|
||||||
5
|
else if(len2 > 0) {
|
||||||
else //both lists are empty
|
implantPadding //need to align for elements
|
||||||
0
|
}
|
||||||
|
else {
|
||||||
|
0 //both lists are empty
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
implicit val codec : Codec[DetailedCharacterData] = (
|
implicit val codec : Codec[DetailedCharacterData] = (
|
||||||
("appearance" | CharacterAppearanceData.codec) ::
|
("appearance" | CharacterAppearanceData.codec) >>:~ { app =>
|
||||||
ignore(160) ::
|
("bep" | uint32L) >>:~ { bep =>
|
||||||
("healthMax" | uint16L) ::
|
("cep" | uint32L) ::
|
||||||
("health" | uint16L) ::
|
ignore(96) ::
|
||||||
ignore(1) ::
|
("healthMax" | uint16L) ::
|
||||||
("armor" | uint16L) ::
|
("health" | uint16L) ::
|
||||||
ignore(9) ::
|
ignore(1) ::
|
||||||
("unk1" | uint8L) ::
|
("armor" | uint16L) ::
|
||||||
ignore(8) ::
|
ignore(9) ::
|
||||||
("unk2" | uint4L) ::
|
("unk1" | uint8L) ::
|
||||||
("unk3" | uintL(3)) ::
|
ignore(8) ::
|
||||||
("staminaMax" | uint16L) ::
|
("unk2" | uint4L) ::
|
||||||
("stamina" | uint16L) ::
|
("unk3" | uintL(3)) ::
|
||||||
ignore(149) ::
|
("staminaMax" | uint16L) ::
|
||||||
("unk4" | uint16L) ::
|
("stamina" | uint16L) ::
|
||||||
("unk5" | uint8L) ::
|
ignore(147) ::
|
||||||
("unk6" | uint8L) ::
|
("certs" | listOfN(uint8L, CertificationType.codec)) ::
|
||||||
("unk7" | uint8L) ::
|
optional(bool, uint32L) :: //ask about sample CCRIDER
|
||||||
("unk8" | uint8L) ::
|
ignore(4) ::
|
||||||
("unk9" | uintL(12)) ::
|
(("implants" | PacketHelpers.listOfNSized(numberOfImplantSlots(bep), implant_entry_codec)) >>:~ { implants =>
|
||||||
ignore(19) ::
|
ignore(12) ::
|
||||||
(("firstTimeEvent_length" | uint32L) >>:~ { len =>
|
(("firstTimeEvent_length" | uint32L) >>:~ { len =>
|
||||||
conditional(len > 0, "firstTimeEvent_firstEntry" | PacketHelpers.encodedStringAligned( ftePadding(len) )) ::
|
conditional(len > 0, "firstTimeEvent_firstEntry" | PacketHelpers.encodedStringAligned(ftePadding(len, implantFieldPadding(implants, CharacterAppearanceData.altModelBit(app))))) ::
|
||||||
("firstTimeEvent_list" | PacketHelpers.listOfNSized(len - 1, PacketHelpers.encodedString)) ::
|
("firstTimeEvent_list" | PacketHelpers.listOfNSized(len - 1, PacketHelpers.encodedString)) ::
|
||||||
(("tutorial_length" | uint32L) >>:~ { len2 =>
|
(("tutorial_length" | uint32L) >>:~ { len2 =>
|
||||||
conditional(len2 > 0, "tutorial_firstEntry" | PacketHelpers.encodedStringAligned( tutPadding(len, len2) )) ::
|
conditional(len2 > 0, "tutorial_firstEntry" | PacketHelpers.encodedStringAligned(tutPadding(len, len2, implantFieldPadding(implants, CharacterAppearanceData.altModelBit(app))))) ::
|
||||||
("tutorial_list" | PacketHelpers.listOfNSized(len2 - 1, PacketHelpers.encodedString)) ::
|
("tutorial_list" | PacketHelpers.listOfNSized(len2 - 1, PacketHelpers.encodedString)) ::
|
||||||
ignore(207) ::
|
ignore(207) ::
|
||||||
optional(bool, "inventory" | InventoryData.codec_detailed) ::
|
optional(bool, "inventory" | InventoryData.codec_detailed) ::
|
||||||
("drawn_slot" | DrawnSlot.codec) ::
|
("drawn_slot" | DrawnSlot.codec) ::
|
||||||
bool //usually false
|
bool //usually false
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
).exmap[DetailedCharacterData] (
|
).exmap[DetailedCharacterData] (
|
||||||
{
|
{
|
||||||
case app :: _ :: b :: c :: _ :: d :: _ :: e :: _ :: f :: g :: h :: i :: _ :: j :: k :: l :: m :: n :: o :: _ :: _ :: q :: r :: _ :: t :: u :: _ :: v :: w :: false :: HNil =>
|
case app :: bep :: cep :: _ :: hpmax :: hp :: _ :: armor :: _ :: u1 :: _ :: u2 :: u3 :: stamax :: stam :: _ :: certs :: _ :: _ :: implants :: _ :: _ :: fte0 :: fte1 :: _ :: tut0 :: tut1 :: _ :: inv :: drawn :: false :: HNil =>
|
||||||
//prepend the displaced first elements to their lists
|
//prepend the displaced first elements to their lists
|
||||||
val fteList : List[String] = if(q.isDefined) { q.get :: r } else r
|
val fteList : List[String] = if(fte0.isDefined) { fte0.get +: fte1 } else fte1
|
||||||
val tutList : List[String] = if(t.isDefined) { t.get :: u } else u
|
val tutList : List[String] = if(tut0.isDefined) { tut0.get +: tut1 } else tut1
|
||||||
Attempt.successful(DetailedCharacterData(app, b, c, d, e, f, g, h, i, j, k, l, m, n, o, fteList, tutList, v, w))
|
Attempt.successful(DetailedCharacterData(app, bep, cep, hpmax, hp, armor, u1, u2, u3, stamax, stam, certs, implants, fteList, tutList, inv, drawn))
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
case DetailedCharacterData(app, b, c, d, e, f, g, h, i, j, k, l, m, n, o, fteList, tutList, p, q) =>
|
case DetailedCharacterData(app, bep, cep, hpmax, hp, armor, u1, u2, u3, stamax, stam, certs, implants, fteList, tutList, inv, drawn) =>
|
||||||
|
val implantCapacity : Int = numberOfImplantSlots(bep)
|
||||||
|
val implantList = if(implants.length > implantCapacity) {
|
||||||
|
implants.slice(0, implantCapacity)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
recursiveEnsureImplantSlots(implantCapacity, implants)
|
||||||
|
}
|
||||||
//shift the first elements off their lists
|
//shift the first elements off their lists
|
||||||
var fteListCopy = fteList
|
val (firstEvent, fteListCopy) = fteList match {
|
||||||
var firstEvent : Option[String] = None
|
case (f : String) +: Nil => (Some(f), Nil)
|
||||||
if(fteList.nonEmpty) {
|
case ((f : String) +: (rest : List[String])) => (Some(f), rest)
|
||||||
firstEvent = Some(fteList.head)
|
case Nil => (None, Nil)
|
||||||
fteListCopy = fteList.drop(1)
|
|
||||||
}
|
}
|
||||||
var tutListCopy = tutList
|
val (firstTutorial, tutListCopy) = tutList match {
|
||||||
var firstTutorial : Option[String] = None
|
case (f : String) +: Nil => (Some(f), Nil)
|
||||||
if(tutList.nonEmpty) {
|
case ((f : String) +: (rest : List[String])) => (Some(f), rest)
|
||||||
firstTutorial = Some(tutList.head)
|
case Nil => (None, Nil)
|
||||||
tutListCopy = tutList.drop(1)
|
|
||||||
}
|
}
|
||||||
Attempt.successful(app :: () :: b :: c :: () :: d :: () :: e :: () :: f :: g :: h :: i :: () :: j :: k :: l :: m :: n :: o :: () :: fteList.size.toLong :: firstEvent :: fteListCopy :: tutList.size.toLong :: firstTutorial :: tutListCopy :: () :: p :: q :: false :: HNil)
|
Attempt.successful(app :: bep :: cep :: () :: hpmax :: hp :: () :: armor :: () :: u1 :: () :: u2 :: u3 :: stamax :: stam :: () :: certs :: None :: () :: implantList :: () :: fteList.size.toLong :: firstEvent :: fteListCopy :: tutList.size.toLong :: firstTutorial :: tutListCopy :: () :: inv :: drawn :: false :: HNil)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
// Copyright (c) 2017 PSForever
|
||||||
|
package net.psforever.types
|
||||||
|
|
||||||
|
import net.psforever.packet.PacketHelpers
|
||||||
|
import scodec.codecs._
|
||||||
|
/**
|
||||||
|
* An `Enumeration` of the available certifications.<br>
|
||||||
|
* <br>
|
||||||
|
* As indicated, the following certifications are always enqueued on an avatar's permissions:
|
||||||
|
* `StandardAssault`, `StandardExoSuit`, `AgileExoSuit`.
|
||||||
|
* They must still be included in any formal lists of permitted equipment for a user.
|
||||||
|
* The other noted certifications require all prerequisite certifications listed or they themselves will not be listed:
|
||||||
|
* `ElectronicsExpert` and `AdvancedEngineering`.
|
||||||
|
* No other certification requires its prerequisites explicitly listed to be listed itself.
|
||||||
|
* Any certification that contains multiple other certifications overrides those individual certifications in the list.
|
||||||
|
* There is no certification for the Advanced Nanite Transport.<br>
|
||||||
|
* <br>
|
||||||
|
* In terms of pricing, `StandardAssault`, `StandardExoSuit`, and `AgileExoSuit` are costless.
|
||||||
|
* A certification that contains multiple other certifications acts as the overriding cost.
|
||||||
|
* (Taking `UniMAX` while owning `AAMAX` will refund the `AAMAX` cost and replace it with the `UniMAX` cost.)
|
||||||
|
*/
|
||||||
|
object CertificationType extends Enumeration {
|
||||||
|
type Type = Value
|
||||||
|
val
|
||||||
|
//0
|
||||||
|
StandardAssault, //always listed
|
||||||
|
MediumAssault,
|
||||||
|
HeavyAssault,
|
||||||
|
SpecialAssault,
|
||||||
|
AntiVehicular,
|
||||||
|
Sniping,
|
||||||
|
EliteAssault,
|
||||||
|
AirCalvaryScout,
|
||||||
|
AirCalvaryInterceptor,
|
||||||
|
AirCalvaryAssault,
|
||||||
|
//10
|
||||||
|
AirSupport,
|
||||||
|
ATV,
|
||||||
|
LightScout,
|
||||||
|
AssaultBuggy,
|
||||||
|
ArmoredAssault1,
|
||||||
|
ArmoredAssault2,
|
||||||
|
GroundTransport,
|
||||||
|
GroundSupport,
|
||||||
|
BattleFrameRobotics,
|
||||||
|
Flail,
|
||||||
|
//20
|
||||||
|
Switchblade,
|
||||||
|
Harasser,
|
||||||
|
Phantasm,
|
||||||
|
GalaxyGunship,
|
||||||
|
BFRAntiAircraft,
|
||||||
|
BFRAntiInfantry,
|
||||||
|
StandardExoSuit, //always listed
|
||||||
|
AgileExoSuit, //always listed
|
||||||
|
ReinforcedExoSuit,
|
||||||
|
InfiltrationSuit,
|
||||||
|
//30
|
||||||
|
AAMAX,
|
||||||
|
AIMAX,
|
||||||
|
AVMAX,
|
||||||
|
UniMAX,
|
||||||
|
Medical,
|
||||||
|
AdvancedMedical,
|
||||||
|
Hacking,
|
||||||
|
AdvancedHacking,
|
||||||
|
ExpertHacking,
|
||||||
|
DataCorruption,
|
||||||
|
//40
|
||||||
|
ElectronicsExpert, //requires Hacking and AdvancedHacking
|
||||||
|
Engineering,
|
||||||
|
CombatEngineering,
|
||||||
|
FortificationEngineering,
|
||||||
|
AssaultEngineering,
|
||||||
|
AdvancedEngineering //requires Engineering and CombatEngineering
|
||||||
|
= Value
|
||||||
|
|
||||||
|
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint8L)
|
||||||
|
}
|
||||||
|
|
@ -9,16 +9,16 @@ import scodec.codecs._
|
||||||
* <br>
|
* <br>
|
||||||
* Implant:<br>
|
* Implant:<br>
|
||||||
* `
|
* `
|
||||||
* 00 - Regeneration (advanced_regen)<br>
|
* 0 - Regeneration (advanced_regen)<br>
|
||||||
* 01 - Enhanced Targeting (targeting)<br>
|
* 1 - Enhanced Targeting (targeting)<br>
|
||||||
* 02 - Audio Amplifier (audio_amplifier)<br>
|
* 2 - Audio Amplifier (audio_amplifier)<br>
|
||||||
* 03 - Darklight Vision (darklight_vision)<br>
|
* 3 - Darklight Vision (darklight_vision)<br>
|
||||||
* 04 - Melee Booster (melee_booster)<br>
|
* 4 - Melee Booster (melee_booster)<br>
|
||||||
* 05 - Personal Shield (personal_shield)<br>
|
* 5 - Personal Shield (personal_shield)<br>
|
||||||
* 06 - Range Magnifier (range_magnifier)<br>
|
* 6 - Range Magnifier (range_magnifier)<br>
|
||||||
* 07 - Second Wind `(na)`<br>
|
* 7 - Second Wind `(na)`<br>
|
||||||
* 08 - Sensor Shield (silent_run)<br>
|
* 8 - Sensor Shield (silent_run)<br>
|
||||||
* 09 - Surge (surge)<br>
|
* 9 - Surge (surge)
|
||||||
* `
|
* `
|
||||||
*/
|
*/
|
||||||
object ImplantType extends Enumeration {
|
object ImplantType extends Enumeration {
|
||||||
|
|
@ -34,5 +34,7 @@ object ImplantType extends Enumeration {
|
||||||
SilentRun,
|
SilentRun,
|
||||||
Surge = Value
|
Surge = Value
|
||||||
|
|
||||||
|
val None = Value(15) //TODO unconfirmed
|
||||||
|
|
||||||
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint4L)
|
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint4L)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ class DisplayedAwardMessageTest extends Specification {
|
||||||
PacketCoding.DecodePacket(string).require match {
|
PacketCoding.DecodePacket(string).require match {
|
||||||
case DisplayedAwardMessage(player_guid, ribbon, bar) =>
|
case DisplayedAwardMessage(player_guid, ribbon, bar) =>
|
||||||
player_guid mustEqual PlanetSideGUID(1695)
|
player_guid mustEqual PlanetSideGUID(1695)
|
||||||
ribbon mustEqual MeritCommendation.TwoYearTR
|
ribbon mustEqual MeritCommendation.TwoYearVS
|
||||||
bar mustEqual RibbonBarsSlot.TermOfService
|
bar mustEqual RibbonBarsSlot.TermOfService
|
||||||
case _ =>
|
case _ =>
|
||||||
ko
|
ko
|
||||||
|
|
@ -22,7 +22,7 @@ class DisplayedAwardMessageTest extends Specification {
|
||||||
}
|
}
|
||||||
|
|
||||||
"encode" in {
|
"encode" in {
|
||||||
val msg = DisplayedAwardMessage(PlanetSideGUID(1695), MeritCommendation.TwoYearTR, RibbonBarsSlot.TermOfService)
|
val msg = DisplayedAwardMessage(PlanetSideGUID(1695), MeritCommendation.TwoYearVS, RibbonBarsSlot.TermOfService)
|
||||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||||
|
|
||||||
pkt mustEqual string
|
pkt mustEqual string
|
||||||
|
|
|
||||||
|
|
@ -206,6 +206,8 @@ class ObjectCreateDetailedMessageTest extends Specification {
|
||||||
char.appearance.ribbons.middle mustEqual MeritCommendation.None
|
char.appearance.ribbons.middle mustEqual MeritCommendation.None
|
||||||
char.appearance.ribbons.lower mustEqual MeritCommendation.None
|
char.appearance.ribbons.lower mustEqual MeritCommendation.None
|
||||||
char.appearance.ribbons.tos mustEqual MeritCommendation.None
|
char.appearance.ribbons.tos mustEqual MeritCommendation.None
|
||||||
|
char.bep mustEqual 0
|
||||||
|
char.cep mustEqual 0
|
||||||
char.healthMax mustEqual 100
|
char.healthMax mustEqual 100
|
||||||
char.health mustEqual 100
|
char.health mustEqual 100
|
||||||
char.armor mustEqual 50 //standard exosuit value
|
char.armor mustEqual 50 //standard exosuit value
|
||||||
|
|
@ -214,12 +216,15 @@ class ObjectCreateDetailedMessageTest extends Specification {
|
||||||
char.unk3 mustEqual 7
|
char.unk3 mustEqual 7
|
||||||
char.staminaMax mustEqual 100
|
char.staminaMax mustEqual 100
|
||||||
char.stamina mustEqual 100
|
char.stamina mustEqual 100
|
||||||
char.unk4 mustEqual 28
|
char.certs.length mustEqual 7
|
||||||
char.unk5 mustEqual 4
|
char.certs.head mustEqual CertificationType.StandardAssault
|
||||||
char.unk6 mustEqual 44
|
char.certs(1) mustEqual CertificationType.MediumAssault
|
||||||
char.unk7 mustEqual 84
|
char.certs(2) mustEqual CertificationType.ATV
|
||||||
char.unk8 mustEqual 104
|
char.certs(3) mustEqual CertificationType.Harasser
|
||||||
char.unk9 mustEqual 1900
|
char.certs(4) mustEqual CertificationType.StandardExoSuit
|
||||||
|
char.certs(5) mustEqual CertificationType.AgileExoSuit
|
||||||
|
char.certs(6) mustEqual CertificationType.ReinforcedExoSuit
|
||||||
|
char.implants.length mustEqual 0
|
||||||
char.firstTimeEvents.size mustEqual 4
|
char.firstTimeEvents.size mustEqual 4
|
||||||
char.firstTimeEvents.head mustEqual "xpe_sanctuary_help"
|
char.firstTimeEvents.head mustEqual "xpe_sanctuary_help"
|
||||||
char.firstTimeEvents(1) mustEqual "xpe_th_firemodes"
|
char.firstTimeEvents(1) mustEqual "xpe_th_firemodes"
|
||||||
|
|
@ -405,14 +410,25 @@ class ObjectCreateDetailedMessageTest extends Specification {
|
||||||
Nil
|
Nil
|
||||||
val obj = DetailedCharacterData(
|
val obj = DetailedCharacterData(
|
||||||
app,
|
app,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
100, 100,
|
100, 100,
|
||||||
50,
|
50,
|
||||||
1, 7, 7,
|
1, 7, 7,
|
||||||
100, 100,
|
100, 100,
|
||||||
28, 4, 44, 84, 104, 1900,
|
List(
|
||||||
|
CertificationType.StandardAssault,
|
||||||
|
CertificationType.MediumAssault,
|
||||||
|
CertificationType.ATV,
|
||||||
|
CertificationType.Harasser,
|
||||||
|
CertificationType.StandardExoSuit,
|
||||||
|
CertificationType.AgileExoSuit,
|
||||||
|
CertificationType.ReinforcedExoSuit
|
||||||
|
),
|
||||||
|
List(),
|
||||||
"xpe_sanctuary_help" :: "xpe_th_firemodes" :: "used_beamer" :: "map13" :: Nil,
|
"xpe_sanctuary_help" :: "xpe_th_firemodes" :: "used_beamer" :: "map13" :: Nil,
|
||||||
List.empty,
|
List.empty,
|
||||||
InventoryData(inv),
|
Some(InventoryData(inv)),
|
||||||
DrawnSlot.Pistol1
|
DrawnSlot.Pistol1
|
||||||
)
|
)
|
||||||
val msg = ObjectCreateDetailedMessage(0x79, PlanetSideGUID(75), obj)
|
val msg = ObjectCreateDetailedMessage(0x79, PlanetSideGUID(75), obj)
|
||||||
|
|
|
||||||
|
|
@ -691,10 +691,10 @@ class ObjectCreateMessageTest extends Specification {
|
||||||
pc.appearance.is_cloaking mustEqual false
|
pc.appearance.is_cloaking mustEqual false
|
||||||
pc.appearance.charging_pose mustEqual false
|
pc.appearance.charging_pose mustEqual false
|
||||||
pc.appearance.on_zipline mustEqual false
|
pc.appearance.on_zipline mustEqual false
|
||||||
pc.appearance.ribbons.upper mustEqual MeritCommendation.Loser4
|
pc.appearance.ribbons.upper mustEqual MeritCommendation.MarkovVeteran
|
||||||
pc.appearance.ribbons.middle mustEqual MeritCommendation.HeavyInfantry3
|
pc.appearance.ribbons.middle mustEqual MeritCommendation.HeavyInfantry4
|
||||||
pc.appearance.ribbons.lower mustEqual MeritCommendation.TankBuster6
|
pc.appearance.ribbons.lower mustEqual MeritCommendation.TankBuster7
|
||||||
pc.appearance.ribbons.tos mustEqual MeritCommendation.SixYearNC
|
pc.appearance.ribbons.tos mustEqual MeritCommendation.SixYearTR
|
||||||
pc.health mustEqual 255
|
pc.health mustEqual 255
|
||||||
pc.armor mustEqual 253
|
pc.armor mustEqual 253
|
||||||
pc.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade
|
pc.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade
|
||||||
|
|
@ -787,10 +787,10 @@ class ObjectCreateMessageTest extends Specification {
|
||||||
pc.appearance.is_cloaking mustEqual false
|
pc.appearance.is_cloaking mustEqual false
|
||||||
pc.appearance.charging_pose mustEqual false
|
pc.appearance.charging_pose mustEqual false
|
||||||
pc.appearance.on_zipline mustEqual false
|
pc.appearance.on_zipline mustEqual false
|
||||||
pc.appearance.ribbons.upper mustEqual MeritCommendation.Jacking
|
pc.appearance.ribbons.upper mustEqual MeritCommendation.Jacking2
|
||||||
pc.appearance.ribbons.middle mustEqual MeritCommendation.ScavengerTR6
|
pc.appearance.ribbons.middle mustEqual MeritCommendation.ScavengerVS1
|
||||||
pc.appearance.ribbons.lower mustEqual MeritCommendation.AMSSupport4
|
pc.appearance.ribbons.lower mustEqual MeritCommendation.AMSSupport4
|
||||||
pc.appearance.ribbons.tos mustEqual MeritCommendation.SixYearTR
|
pc.appearance.ribbons.tos mustEqual MeritCommendation.SixYearVS
|
||||||
pc.health mustEqual 0
|
pc.health mustEqual 0
|
||||||
pc.armor mustEqual 0
|
pc.armor mustEqual 0
|
||||||
pc.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade
|
pc.uniform_upgrade mustEqual UniformStyle.ThirdUpgrade
|
||||||
|
|
@ -1115,10 +1115,10 @@ class ObjectCreateMessageTest extends Specification {
|
||||||
GrenadeState.None,
|
GrenadeState.None,
|
||||||
false, false, false,
|
false, false, false,
|
||||||
RibbonBars(
|
RibbonBars(
|
||||||
MeritCommendation.Loser4,
|
MeritCommendation.MarkovVeteran,
|
||||||
MeritCommendation.HeavyInfantry3,
|
MeritCommendation.HeavyInfantry4,
|
||||||
MeritCommendation.TankBuster6,
|
MeritCommendation.TankBuster7,
|
||||||
MeritCommendation.SixYearNC
|
MeritCommendation.SixYearTR
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
255, 253,
|
255, 253,
|
||||||
|
|
@ -1172,10 +1172,10 @@ class ObjectCreateMessageTest extends Specification {
|
||||||
GrenadeState.None,
|
GrenadeState.None,
|
||||||
false, false, false,
|
false, false, false,
|
||||||
RibbonBars(
|
RibbonBars(
|
||||||
MeritCommendation.Jacking,
|
MeritCommendation.Jacking2,
|
||||||
MeritCommendation.ScavengerTR6,
|
MeritCommendation.ScavengerVS1,
|
||||||
MeritCommendation.AMSSupport4,
|
MeritCommendation.AMSSupport4,
|
||||||
MeritCommendation.SixYearTR
|
MeritCommendation.SixYearVS
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
0, 0,
|
0, 0,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package objects
|
package objects
|
||||||
|
|
||||||
import net.psforever.objects.Implant
|
import net.psforever.objects.ImplantSlot
|
||||||
import net.psforever.objects.definition.{ImplantDefinition, Stance}
|
import net.psforever.objects.definition.{ImplantDefinition, Stance}
|
||||||
import net.psforever.types.{ExoSuitType, ImplantType}
|
import net.psforever.types.{ExoSuitType, ImplantType}
|
||||||
import org.specs2.mutable._
|
import org.specs2.mutable._
|
||||||
|
|
@ -16,61 +16,122 @@ class ImplantTest extends Specification {
|
||||||
sample.DurationChargeByExoSuit += ExoSuitType.Standard -> 1
|
sample.DurationChargeByExoSuit += ExoSuitType.Standard -> 1
|
||||||
sample.DurationChargeByStance += Stance.Running -> 1
|
sample.DurationChargeByStance += Stance.Running -> 1
|
||||||
|
|
||||||
"define" in {
|
"ImplantDefinition" should {
|
||||||
sample.Initialization mustEqual 90000
|
"define" in {
|
||||||
sample.ActivationCharge mustEqual 3
|
sample.Initialization mustEqual 90000
|
||||||
sample.DurationChargeBase mustEqual 1
|
sample.ActivationCharge mustEqual 3
|
||||||
sample.DurationChargeByExoSuit(ExoSuitType.Agile) mustEqual 2
|
sample.DurationChargeBase mustEqual 1
|
||||||
sample.DurationChargeByExoSuit(ExoSuitType.Reinforced) mustEqual 2
|
sample.DurationChargeByExoSuit(ExoSuitType.Agile) mustEqual 2
|
||||||
sample.DurationChargeByExoSuit(ExoSuitType.Standard) mustEqual 1
|
sample.DurationChargeByExoSuit(ExoSuitType.Reinforced) mustEqual 2
|
||||||
sample.DurationChargeByExoSuit(ExoSuitType.Infiltration) mustEqual 0 //default value
|
sample.DurationChargeByExoSuit(ExoSuitType.Standard) mustEqual 1
|
||||||
sample.DurationChargeByStance(Stance.Running) mustEqual 1
|
sample.DurationChargeByExoSuit(ExoSuitType.Infiltration) mustEqual 0 //default value
|
||||||
sample.DurationChargeByStance(Stance.Crouching) mustEqual 0 //default value
|
sample.DurationChargeByStance(Stance.Running) mustEqual 1
|
||||||
sample.Type mustEqual ImplantType.SilentRun
|
sample.DurationChargeByStance(Stance.Crouching) mustEqual 0 //default value
|
||||||
|
sample.Type mustEqual ImplantType.SilentRun
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"construct" in {
|
"ImplantSlot" should {
|
||||||
val obj = new Implant(sample)
|
"construct" in {
|
||||||
obj.Definition.Type mustEqual sample.Type
|
val obj = new ImplantSlot
|
||||||
obj.Active mustEqual false
|
obj.Unlocked mustEqual false
|
||||||
obj.Ready mustEqual false
|
obj.Initialized mustEqual false
|
||||||
obj.Timer mustEqual 0
|
obj.Active mustEqual false
|
||||||
}
|
obj.Implant mustEqual ImplantType.None
|
||||||
|
obj.Installed mustEqual None
|
||||||
|
}
|
||||||
|
|
||||||
"reset/init their timer" in {
|
"load an implant when locked" in {
|
||||||
val obj = new Implant(sample)
|
val obj = new ImplantSlot
|
||||||
obj.Timer mustEqual 0
|
obj.Unlocked mustEqual false
|
||||||
obj.Reset()
|
obj.Implant mustEqual ImplantType.None
|
||||||
obj.Timer mustEqual 90000
|
|
||||||
}
|
|
||||||
|
|
||||||
"reset/init their readiness condition" in {
|
obj.Implant = sample
|
||||||
val obj = new Implant(sample)
|
obj.Implant mustEqual ImplantType.None
|
||||||
obj.Ready mustEqual false
|
}
|
||||||
obj.Timer = 0
|
|
||||||
obj.Ready mustEqual true
|
|
||||||
obj.Reset()
|
|
||||||
obj.Ready mustEqual false
|
|
||||||
}
|
|
||||||
|
|
||||||
"not activate until they are ready" in {
|
"load an implant when unlocked" in {
|
||||||
val obj = new Implant(sample)
|
val obj = new ImplantSlot
|
||||||
obj.Active = true
|
obj.Unlocked mustEqual false
|
||||||
obj.Active mustEqual false
|
obj.Implant mustEqual ImplantType.None
|
||||||
obj.Timer = 0
|
sample.Type mustEqual ImplantType.SilentRun
|
||||||
obj.Active = true
|
|
||||||
obj.Active mustEqual true
|
|
||||||
}
|
|
||||||
|
|
||||||
"not cost energy while not active" in {
|
obj.Unlocked = true
|
||||||
val obj = new Implant(sample)
|
obj.Implant = sample
|
||||||
obj.Charge(ExoSuitType.Reinforced, Stance.Running) mustEqual 0
|
obj.Implant mustEqual ImplantType.SilentRun
|
||||||
}
|
}
|
||||||
|
|
||||||
"cost energy while active" in {
|
"can not re-lock an unlocked implant slot" in {
|
||||||
val obj = new Implant(sample)
|
val obj = new ImplantSlot
|
||||||
obj.Timer = 0
|
obj.Unlocked mustEqual false
|
||||||
obj.Active = true
|
|
||||||
obj.Charge(ExoSuitType.Reinforced, Stance.Running) mustEqual 4
|
obj.Unlocked = false
|
||||||
|
obj.Unlocked mustEqual false
|
||||||
|
obj.Unlocked = true
|
||||||
|
obj.Unlocked mustEqual true
|
||||||
|
obj.Unlocked = false
|
||||||
|
obj.Unlocked mustEqual true
|
||||||
|
}
|
||||||
|
|
||||||
|
"initialize without an implant" in {
|
||||||
|
val obj = new ImplantSlot
|
||||||
|
obj.Initialized mustEqual false
|
||||||
|
obj.Initialized = true
|
||||||
|
obj.Initialized mustEqual false
|
||||||
|
}
|
||||||
|
|
||||||
|
"initialize an implant" in {
|
||||||
|
val obj = new ImplantSlot
|
||||||
|
obj.Initialized mustEqual false
|
||||||
|
|
||||||
|
obj.Unlocked = true
|
||||||
|
obj.Implant = sample
|
||||||
|
obj.Initialized = true
|
||||||
|
obj.Initialized mustEqual true
|
||||||
|
}
|
||||||
|
|
||||||
|
"activate an uninitialized implant" in {
|
||||||
|
val obj = new ImplantSlot
|
||||||
|
obj.Unlocked = true
|
||||||
|
obj.Implant = sample
|
||||||
|
obj.Initialized mustEqual false
|
||||||
|
obj.Active mustEqual false
|
||||||
|
|
||||||
|
obj.Active = true
|
||||||
|
obj.Active mustEqual false
|
||||||
|
}
|
||||||
|
|
||||||
|
"activate an initialized implant" in {
|
||||||
|
val obj = new ImplantSlot
|
||||||
|
obj.Unlocked = true
|
||||||
|
obj.Implant = sample
|
||||||
|
obj.Initialized mustEqual false
|
||||||
|
obj.Active mustEqual false
|
||||||
|
|
||||||
|
obj.Initialized = true
|
||||||
|
obj.Active = true
|
||||||
|
obj.Active mustEqual true
|
||||||
|
}
|
||||||
|
|
||||||
|
"not cost energy while not active" in {
|
||||||
|
val obj = new ImplantSlot
|
||||||
|
obj.Unlocked = true
|
||||||
|
obj.Implant = sample
|
||||||
|
obj.Initialized = true
|
||||||
|
obj.Active mustEqual false
|
||||||
|
obj.ActivationCharge mustEqual 0
|
||||||
|
obj.Charge(ExoSuitType.Reinforced, Stance.Running) mustEqual 0
|
||||||
|
}
|
||||||
|
|
||||||
|
"cost energy while active" in {
|
||||||
|
val obj = new ImplantSlot
|
||||||
|
obj.Unlocked = true
|
||||||
|
obj.Implant = sample
|
||||||
|
obj.Initialized = true
|
||||||
|
obj.Active = true
|
||||||
|
obj.Active mustEqual true
|
||||||
|
obj.ActivationCharge mustEqual 3
|
||||||
|
obj.Charge(ExoSuitType.Reinforced, Stance.Running) mustEqual 4
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package objects
|
package objects
|
||||||
|
|
||||||
import net.psforever.objects.{Implant, Player, SimpleItem}
|
import net.psforever.objects.{Player, SimpleItem}
|
||||||
import net.psforever.objects.definition.{ImplantDefinition, SimpleItemDefinition}
|
import net.psforever.objects.definition.{ImplantDefinition, SimpleItemDefinition}
|
||||||
import net.psforever.objects.equipment.EquipmentSize
|
import net.psforever.objects.equipment.EquipmentSize
|
||||||
import net.psforever.types.{CharacterGender, ExoSuitType, ImplantType, PlanetSideEmpire}
|
import net.psforever.types.{CharacterGender, ExoSuitType, ImplantType, PlanetSideEmpire}
|
||||||
|
|
@ -106,31 +106,39 @@ class PlayerTest extends Specification {
|
||||||
obj.LastDrawnSlot mustEqual 1
|
obj.LastDrawnSlot mustEqual 1
|
||||||
}
|
}
|
||||||
|
|
||||||
"install no implants until a slot is unlocked" in {
|
"install an implant" in {
|
||||||
val testplant : Implant = Implant(ImplantDefinition(1))
|
val testplant : ImplantDefinition = ImplantDefinition(1)
|
||||||
val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
|
val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
|
||||||
obj.Implants(0).Unlocked mustEqual false
|
|
||||||
obj.Implant(0) mustEqual None
|
|
||||||
obj.InstallImplant(testplant)
|
|
||||||
obj.Implant(0) mustEqual None
|
|
||||||
obj.Implant(ImplantType(1)) mustEqual None
|
|
||||||
|
|
||||||
obj.Implants(0).Unlocked = true
|
obj.Implants(0).Unlocked = true
|
||||||
obj.InstallImplant(testplant)
|
obj.InstallImplant(testplant) mustEqual true
|
||||||
obj.Implant(0) mustEqual Some(testplant.Definition.Type)
|
obj.Implants.find({p => p.Implant == ImplantType(1)}) match { //find the installed implant
|
||||||
obj.Implant(ImplantType(1)) mustEqual Some(testplant)
|
case Some(slot) =>
|
||||||
|
slot.Installed mustEqual Some(testplant)
|
||||||
|
case _ =>
|
||||||
|
ko
|
||||||
|
}
|
||||||
|
ok
|
||||||
|
}
|
||||||
|
|
||||||
|
"can not install the same type of implant twice" in {
|
||||||
|
val testplant1 : ImplantDefinition = ImplantDefinition(1)
|
||||||
|
val testplant2 : ImplantDefinition = ImplantDefinition(1)
|
||||||
|
val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
|
||||||
|
obj.Implants(0).Unlocked = true
|
||||||
|
obj.Implants(1).Unlocked = true
|
||||||
|
obj.InstallImplant(testplant1) mustEqual true
|
||||||
|
obj.InstallImplant(testplant2) mustEqual false
|
||||||
}
|
}
|
||||||
|
|
||||||
"uninstall implants" in {
|
"uninstall implants" in {
|
||||||
val testplant : Implant = Implant(ImplantDefinition(1))
|
val testplant : ImplantDefinition = ImplantDefinition(1)
|
||||||
val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
|
val obj = new Player("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, 5)
|
||||||
obj.Implants(0).Unlocked = true
|
obj.Implants(0).Unlocked = true
|
||||||
obj.InstallImplant(testplant)
|
obj.InstallImplant(testplant) mustEqual true
|
||||||
obj.Implant(ImplantType(1)) mustEqual Some(testplant)
|
obj.Implants(0).Installed mustEqual Some(testplant)
|
||||||
|
|
||||||
obj.UninstallImplant(ImplantType(1))
|
obj.UninstallImplant(testplant.Type)
|
||||||
obj.Implant(0) mustEqual None
|
obj.Implants(0).Installed mustEqual None
|
||||||
obj.Implant(ImplantType(1)) mustEqual None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"administrate" in {
|
"administrate" in {
|
||||||
|
|
|
||||||
|
|
@ -601,6 +601,13 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
player = Player("IlllIIIlllIlIllIlllIllI", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1)
|
player = Player("IlllIIIlllIlIllIlllIllI", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1)
|
||||||
player.Position = Vector3(3674.8438f, 2726.789f, 91.15625f)
|
player.Position = Vector3(3674.8438f, 2726.789f, 91.15625f)
|
||||||
player.Orientation = Vector3(0f, 0f, 90f)
|
player.Orientation = Vector3(0f, 0f, 90f)
|
||||||
|
player.Certifications += CertificationType.StandardAssault
|
||||||
|
player.Certifications += CertificationType.MediumAssault
|
||||||
|
player.Certifications += CertificationType.StandardExoSuit
|
||||||
|
player.Certifications += CertificationType.AgileExoSuit
|
||||||
|
player.Certifications += CertificationType.ReinforcedExoSuit
|
||||||
|
player.Certifications += CertificationType.ATV
|
||||||
|
player.Certifications += CertificationType.Harasser
|
||||||
player.Slot(0).Equipment = beamer1
|
player.Slot(0).Equipment = beamer1
|
||||||
player.Slot(2).Equipment = suppressor1
|
player.Slot(2).Equipment = suppressor1
|
||||||
player.Slot(4).Equipment = forceblade1
|
player.Slot(4).Equipment = forceblade1
|
||||||
|
|
@ -902,6 +909,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
case msg @ ChangeAmmoMessage(item_guid, unk1) =>
|
case msg @ ChangeAmmoMessage(item_guid, unk1) =>
|
||||||
log.info("ChangeAmmo: " + msg)
|
log.info("ChangeAmmo: " + msg)
|
||||||
|
|
||||||
|
case msg @ AvatarImplantMessage(_, _, _, _) => //(player_guid, unk1, unk2, implant) =>
|
||||||
|
log.info("AvatarImplantMessage: " + msg)
|
||||||
|
|
||||||
case msg @ UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType) =>
|
case msg @ UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType) =>
|
||||||
log.info("UseItem: " + msg)
|
log.info("UseItem: " + msg)
|
||||||
// TODO: Not all fields in the response are identical to source in real packet logs (but seems to be ok)
|
// TODO: Not all fields in the response are identical to source in real packet logs (but seems to be ok)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue