ObjectCreateMessage Alterations, Class Object Adjustments (#243)

* power outage failure resulting in the destruction of the original ocm-fixes branch; the git branch refs were corrupted during commit, but the up-to-date changed files remained intact

* eliminating the need for CommonFieldData2 and CommonFieldData2WithPlacement

* in the middle of integrating CommonFieldData into DetailedLockerContainerData (but not standard LockerContainerData); added field for final boolean in WeaponData

* adding faction affinity to Equipment (to match functionality; not becuase I know what ends ...)

* in the middle of integrating CommonFieldData into DetailedCommandDetonaterData

* applying faction affinity to objects at time of terminal production (but to what ends?); required BoomerTrigger and AmmoBox to always report as NEUTRAL internally

* completed the transition from using the old class-based order terminal system to the page-based order terminal system; unused terminal classes have been eliminated

* more closely aligned TelepadDeployableData and InternalTelepadDeployableData

* modifying TelepadDeployableData make it generic and eliminate the need for InternalTelepadDeployableData after fixing a packet converter to utilize DroppedItemData

* modified Terminal operation to branch further outwards from Terminal.Request to the TerminalDefinition's Request method; modified tests to reflect update

* loosening up matrix terminal definition limitations

* modified ProximityTerminal to support a custom defintition class

* rendered the message passing system for Terminals general (Any) in the full scale so it can be specific in instance cases

* refactored and moved both EquipmentSlot and ExoSuitDefinition

* (re)load Favorites each time player (re)spawns
This commit is contained in:
Fate-JH 2019-03-03 08:23:30 -05:00 committed by GitHub
parent 5fc9e191fe
commit 337cfbe5d2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
174 changed files with 6281 additions and 5477 deletions

View file

@ -3,6 +3,7 @@ package net.psforever.objects
import net.psforever.objects.definition.AmmoBoxDefinition
import net.psforever.objects.equipment.{Ammo, Equipment}
import net.psforever.types.PlanetSideEmpire
class AmmoBox(private val ammoDef : AmmoBoxDefinition,
cap : Option[Int] = None
@ -22,6 +23,8 @@ class AmmoBox(private val ammoDef : AmmoBoxDefinition,
def Definition : AmmoBoxDefinition = ammoDef
override def Faction_=(fact : PlanetSideEmpire.Value) : PlanetSideEmpire.Value = Faction
override def toString : String = {
AmmoBox.toString(this)
}

View file

@ -3,8 +3,9 @@ package net.psforever.objects
import net.psforever.objects.avatar.DeployableToolbox
import net.psforever.objects.definition.{AvatarDefinition, ImplantDefinition}
import net.psforever.objects.equipment.EquipmentSize
import net.psforever.objects.equipment.{EquipmentSize, EquipmentSlot}
import net.psforever.objects.loadouts.Loadout
import net.psforever.packet.game.objectcreate.Cosmetics
import net.psforever.types._
import scala.annotation.tailrec
@ -15,6 +16,8 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex :
private var bep : Long = 0
/** Command Experience Points */
private var cep : Long = 0
/** Cosmetics **/
private var pStyle : Option[Cosmetics] = None
/** Certifications */
private val certs : mutable.Set[CertificationType.Value] = mutable.Set[CertificationType.Value]()
/** Implants<br>
@ -56,6 +59,13 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex :
CEP
}
def PersonalStyleFeatures : Option[Cosmetics] = pStyle
def PersonalStyleFeatures_=(app : Cosmetics) : Option[Cosmetics] = {
pStyle = Some(app)
pStyle
}
/**
* Retrieve the three implant slots for this player.
* @return an `Array` of `ImplantSlot` objects
@ -166,12 +176,14 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex :
}
}
def LoadLoadout(line : Int) : Option[Loadout] = loadouts.lift(line).getOrElse(None)
def LoadLoadout(line : Int) : Option[Loadout] = loadouts.lift(line).flatten
def DeleteLoadout(line : Int) : Unit = {
loadouts(line) = None
}
def Loadouts : Seq[(Int, Loadout)] = loadouts.zipWithIndex.collect { case(Some(loadout), index) => (index, loadout) } toSeq
def Locker : LockerContainer = locker
def FifthSlot : EquipmentSlot = {

View file

@ -2,5 +2,8 @@
package net.psforever.objects
import net.psforever.objects.equipment.RemoteUnit
import net.psforever.types.PlanetSideEmpire
class BoomerTrigger extends SimpleItem(GlobalDefinitions.boomer_trigger) with RemoteUnit
class BoomerTrigger extends SimpleItem(GlobalDefinitions.boomer_trigger) with RemoteUnit {
override def Faction_=(fact : PlanetSideEmpire.Value) : PlanetSideEmpire.Value = Faction
}

View file

@ -17,8 +17,8 @@ import net.psforever.objects.serverobject.tube.SpawnTubeDefinition
import net.psforever.objects.serverobject.resourcesilo.ResourceSiloDefinition
import net.psforever.objects.serverobject.turret.{TurretDefinition, TurretUpgrade}
import net.psforever.objects.vehicles.{DestroyedVehicle, SeatArmorRestriction, UtilityType}
import net.psforever.objects.vital.{DamageType, StandardResolutions}
import net.psforever.types.{CertificationType, PlanetSideEmpire, Vector3}
import net.psforever.objects.vital.{DamageType, StandardMaxDamage, StandardResolutions}
import net.psforever.types.{CertificationType, ExoSuitType, PlanetSideEmpire, Vector3}
import scala.collection.mutable
import scala.concurrent.duration._
@ -29,6 +29,71 @@ object GlobalDefinitions {
*/
val avatar = new AvatarDefinition(121)
/*
Exo-suits
*/
val Standard = ExoSuitDefinition(ExoSuitType.Standard)
Standard.Name = "standard"
Standard.MaxArmor = 50
Standard.InventoryScale = InventoryTile.Tile96
Standard.InventoryOffset = 6
Standard.Holster(0, EquipmentSize.Pistol)
Standard.Holster(2, EquipmentSize.Rifle)
Standard.Holster(4, EquipmentSize.Melee)
Standard.ResistanceDirectHit = 4
Standard.ResistanceSplash = 15
Standard.ResistanceAggravated = 8
val Agile = ExoSuitDefinition(ExoSuitType.Agile)
Agile.Name = "agile"
Agile.MaxArmor = 100
Agile.InventoryScale = InventoryTile.Tile99
Agile.InventoryOffset = 6
Agile.Holster(0, EquipmentSize.Pistol)
Agile.Holster(1, EquipmentSize.Pistol)
Agile.Holster(2, EquipmentSize.Rifle)
Agile.Holster(4, EquipmentSize.Melee)
Agile.ResistanceDirectHit = 6
Agile.ResistanceSplash = 25
Agile.ResistanceAggravated = 10
val Reinforced = ExoSuitDefinition(ExoSuitType.Reinforced)
Reinforced.Name = "reinforced"
Reinforced.Permissions = List(CertificationType.ReinforcedExoSuit)
Reinforced.MaxArmor = 200
Reinforced.InventoryScale = InventoryTile.Tile1209
Reinforced.InventoryOffset = 6
Reinforced.Holster(0, EquipmentSize.Pistol)
Reinforced.Holster(1, EquipmentSize.Pistol)
Reinforced.Holster(2, EquipmentSize.Rifle)
Reinforced.Holster(3, EquipmentSize.Rifle)
Reinforced.Holster(4, EquipmentSize.Melee)
Reinforced.ResistanceDirectHit = 10
Reinforced.ResistanceSplash = 35
Reinforced.ResistanceAggravated = 12
val Infiltration = ExoSuitDefinition(ExoSuitType.Infiltration)
Infiltration.Name = "infiltration_suit"
Infiltration.Permissions = List(CertificationType.InfiltrationSuit)
Infiltration.MaxArmor = 0
Infiltration.InventoryScale = InventoryTile.Tile66
Infiltration.InventoryOffset = 6
Infiltration.Holster(0, EquipmentSize.Pistol)
Infiltration.Holster(4, EquipmentSize.Melee)
val MAX = SpecialExoSuitDefinition(ExoSuitType.MAX)
MAX.Permissions = List(CertificationType.AIMAX,CertificationType.AVMAX, CertificationType.AAMAX, CertificationType.UniMAX)
MAX.MaxArmor = 650
MAX.InventoryScale = InventoryTile.Tile1612
MAX.InventoryOffset = 6
MAX.Holster(0, EquipmentSize.Max)
MAX.Holster(4, EquipmentSize.Melee)
MAX.Subtract.Damage1 = -2
MAX.ResistanceDirectHit = 6
MAX.ResistanceSplash = 35
MAX.ResistanceAggravated = 10
MAX.Damage = StandardMaxDamage
MAX.Model = StandardResolutions.Max
/*
Implants
*/
val advanced_regen = ImplantDefinition(0)
@ -880,37 +945,44 @@ object GlobalDefinitions {
/*
Miscellaneous
*/
val order_terminal = new OrderTerminalDefinition
val ams_respawn_tube = new SpawnTubeDefinition(49)
val matrix_terminala = new MatrixTerminalDefinition(517)
val matrix_terminalb = new MatrixTerminalDefinition(518)
val matrix_terminalc = new MatrixTerminalDefinition(519)
val order_terminala = new OrderTerminalABDefinition(613)
val spawn_terminal = new MatrixTerminalDefinition(812)
val order_terminalb = new OrderTerminalABDefinition(614)
val order_terminal = new OrderTerminalDefinition(612)
val cert_terminal = new CertTerminalDefinition
val order_terminala = new OrderTerminalDefinition(613)
val order_terminalb = new OrderTerminalDefinition(614)
val cert_terminal = new OrderTerminalDefinition(171)
val implant_terminal_mech = new ImplantTerminalMechDefinition
val implant_terminal_interface = new ImplantTerminalInterfaceDefinition
val implant_terminal_interface = new OrderTerminalDefinition(409)
val ground_vehicle_terminal = new GroundVehicleTerminalDefinition
val ground_vehicle_terminal = new OrderTerminalDefinition(386)
val air_vehicle_terminal = new AirVehicleTerminalDefinition
val air_vehicle_terminal = new OrderTerminalDefinition(43)
val dropship_vehicle_terminal = new DropshipVehicleTerminalDefinition
val dropship_vehicle_terminal = new OrderTerminalDefinition(263)
val vehicle_terminal_combined = new VehicleTerminalCombinedDefinition
val vehicle_terminal_combined = new OrderTerminalDefinition(952)
val spawn_terminal = new MatrixTerminalDefinition(812)
val bfr_terminal = new OrderTerminalDefinition(143)
val respawn_tube = new SpawnTubeDefinition(732)
val respawn_tube_tower = new SpawnTubeDefinition(733)
val teleportpad_terminal = new TeleportPadTerminalDefinition
val teleportpad_terminal = new OrderTerminalDefinition(853)
val adv_med_terminal = new MedicalTerminalDefinition(38)
@ -944,13 +1016,13 @@ object GlobalDefinitions {
val lodestar_repair_terminal = new MedicalTerminalDefinition(461)
val multivehicle_rearm_terminal = new _OrderTerminalDefinition(576)
val multivehicle_rearm_terminal = new OrderTerminalDefinition(576)
val bfr_rearm_terminal = new _OrderTerminalDefinition(142)
val bfr_rearm_terminal = new OrderTerminalDefinition(142)
val air_rearm_terminal = new _OrderTerminalDefinition(42)
val air_rearm_terminal = new OrderTerminalDefinition(42)
val ground_rearm_terminal = new _OrderTerminalDefinition(384)
val ground_rearm_terminal = new OrderTerminalDefinition(384)
val manned_turret = new TurretDefinition(480)
initMiscellaneous()
@ -1338,6 +1410,68 @@ object GlobalDefinitions {
super_staminakit.Name = "super_staminakit"
}
/**
* Initialize `ExoSuitType` globals.
*/
private def init_exosuit() : Unit = {
Standard.Name = "standard"
Standard.MaxArmor = 50
Standard.InventoryScale = InventoryTile.Tile96
Standard.InventoryOffset = 6
Standard.Holster(0, EquipmentSize.Pistol)
Standard.Holster(2, EquipmentSize.Rifle)
Standard.Holster(4, EquipmentSize.Melee)
Standard.ResistanceDirectHit = 4
Standard.ResistanceSplash = 15
Standard.ResistanceAggravated = 8
Agile.Name = "lite_armor"
Agile.MaxArmor = 100
Agile.InventoryScale = InventoryTile.Tile99
Agile.InventoryOffset = 6
Agile.Holster(0, EquipmentSize.Pistol)
Agile.Holster(1, EquipmentSize.Pistol)
Agile.Holster(2, EquipmentSize.Rifle)
Agile.Holster(4, EquipmentSize.Melee)
Agile.ResistanceDirectHit = 6
Agile.ResistanceSplash = 25
Agile.ResistanceAggravated = 10
Reinforced.Name = "med_armor"
Reinforced.Permissions = List(CertificationType.ReinforcedExoSuit)
Reinforced.MaxArmor = 200
Reinforced.InventoryScale = InventoryTile.Tile1209
Reinforced.InventoryOffset = 6
Reinforced.Holster(0, EquipmentSize.Pistol)
Reinforced.Holster(1, EquipmentSize.Pistol)
Reinforced.Holster(2, EquipmentSize.Rifle)
Reinforced.Holster(3, EquipmentSize.Rifle)
Reinforced.Holster(4, EquipmentSize.Melee)
Reinforced.ResistanceDirectHit = 10
Reinforced.ResistanceSplash = 35
Reinforced.ResistanceAggravated = 12
Infiltration.Name = "infiltration_suit"
Infiltration.Permissions = List(CertificationType.InfiltrationSuit)
Infiltration.MaxArmor = 0
Infiltration.InventoryScale = InventoryTile.Tile66
Infiltration.InventoryOffset = 6
Infiltration.Holster(0, EquipmentSize.Pistol)
Infiltration.Holster(4, EquipmentSize.Melee)
MAX.Permissions = List(CertificationType.AIMAX,CertificationType.AVMAX, CertificationType.AAMAX, CertificationType.UniMAX)
MAX.MaxArmor = 650
MAX.InventoryScale = InventoryTile.Tile1612
MAX.InventoryOffset = 6
MAX.Holster(0, EquipmentSize.Max)
MAX.Holster(4, EquipmentSize.Melee)
MAX.Subtract.Damage1 = -2
MAX.ResistanceDirectHit = 6
MAX.ResistanceSplash = 35
MAX.ResistanceAggravated = 10
MAX.Damage = StandardMaxDamage
MAX.Model = StandardResolutions.Max
}
/**
* Initialize `AmmoBoxDefinition` globals.
*/
@ -5578,7 +5712,7 @@ object GlobalDefinitions {
deployable_shield_generator.MaxHealth = 1700
deployable_shield_generator.DeployTime = Duration.create(6000, "ms")
deployable_shield_generator.Model = StandardResolutions.ComplexDeployables
router_telepad_deployable.Name = "router_telepad_deployable"
router_telepad_deployable.MaxHealth = 100
router_telepad_deployable.DeployTime = Duration.create(1, "ms")
@ -5595,6 +5729,67 @@ object GlobalDefinitions {
* Initialize `Miscellaneous` globals.
*/
private def initMiscellaneous() : Unit = {
matrix_terminala.Name = "matrix_terminala"
matrix_terminalb.Name = "matrix_terminalb"
matrix_terminalc.Name = "matrix_terminalc"
spawn_terminal.Name = "spawn_terminal"
order_terminal.Name = "order_terminal"
order_terminal.Tab += 0 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.infantryAmmunition ++ EquipmentTerminalDefinition.infantryWeapons)
order_terminal.Tab += 1 -> OrderTerminalDefinition.ArmorWithAmmoPage(EquipmentTerminalDefinition.suits ++ EquipmentTerminalDefinition.maxSuits, EquipmentTerminalDefinition.maxAmmo)
order_terminal.Tab += 2 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.supportAmmunition ++ EquipmentTerminalDefinition.supportWeapons)
order_terminal.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
order_terminal.Tab += 4 -> OrderTerminalDefinition.InfantryLoadoutPage()
order_terminal.SellEquipmentByDefault = true
order_terminala.Name = "order_terminala"
order_terminala.Tab += 0 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.infantryAmmunition ++ EquipmentTerminalDefinition.infantryWeapons)
order_terminala.Tab += 1 -> OrderTerminalDefinition.ArmorWithAmmoPage(EquipmentTerminalDefinition.suits, EquipmentTerminalDefinition.maxAmmo)
order_terminala.Tab += 2 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.supportAmmunition ++ EquipmentTerminalDefinition.supportWeapons)
order_terminala.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
order_terminala.Tab += 4 -> OrderTerminalDefinition.InfantryLoadoutPage()
order_terminala.SellEquipmentByDefault = true
order_terminalb.Name = "order_terminalb"
order_terminalb.Tab += 0 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.infantryAmmunition ++ EquipmentTerminalDefinition.infantryWeapons)
order_terminalb.Tab += 1 -> OrderTerminalDefinition.ArmorWithAmmoPage(EquipmentTerminalDefinition.suits, EquipmentTerminalDefinition.maxAmmo)
order_terminalb.Tab += 2 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.supportAmmunition ++ EquipmentTerminalDefinition.supportWeapons)
order_terminalb.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
order_terminalb.Tab += 4 -> OrderTerminalDefinition.InfantryLoadoutPage()
order_terminalb.SellEquipmentByDefault = true
cert_terminal.Name = "cert_terminal"
cert_terminal.Tab += 0 -> OrderTerminalDefinition.CertificationPage(CertTerminalDefinition.certs)
implant_terminal_interface.Name = "implant_terminal_interface"
implant_terminal_interface.Tab += 0 -> OrderTerminalDefinition.ImplantPage(ImplantTerminalDefinition.implants)
ground_vehicle_terminal.Name = "ground_vehicle_terminal"
ground_vehicle_terminal.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.groundVehicles, VehicleTerminalDefinition.trunk)
ground_vehicle_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
air_vehicle_terminal.Name = "air_vehicle_terminal"
air_vehicle_terminal.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.flight1Vehicles, VehicleTerminalDefinition.trunk)
air_vehicle_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
dropship_vehicle_terminal.Name = "dropship_vehicle_terminal"
dropship_vehicle_terminal.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.flight1Vehicles ++ VehicleTerminalDefinition.flight2Vehicles, VehicleTerminalDefinition.trunk)
dropship_vehicle_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
vehicle_terminal_combined.Name = "vehicle_terminal_combined"
vehicle_terminal_combined.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.flight1Vehicles ++ VehicleTerminalDefinition.groundVehicles, VehicleTerminalDefinition.trunk)
vehicle_terminal_combined.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
bfr_terminal.Name = "bfr_terminal"
bfr_terminal.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.bfrVehicles, VehicleTerminalDefinition.trunk)
bfr_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
teleportpad_terminal.Name = "teleportpad_terminal"
teleportpad_terminal.Tab += 0 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.routerTerminal)
adv_med_terminal.Name = "adv_med_terminal"
adv_med_terminal.Interval = 500
adv_med_terminal.HealAmount = 8
@ -5653,20 +5848,24 @@ object GlobalDefinitions {
lodestar_repair_terminal.TargetValidation += ProximityTarget.Vehicle -> ProximityTerminalControl.Validation.RepairSilo
multivehicle_rearm_terminal.Name = "multivehicle_rearm_terminal"
multivehicle_rearm_terminal.Page += 3 -> _OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
multivehicle_rearm_terminal.Page += 4 -> _OrderTerminalDefinition.VehicleLoadoutPage()
multivehicle_rearm_terminal.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
multivehicle_rearm_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
multivehicle_rearm_terminal.SellEquipmentByDefault = true //TODO ?
bfr_rearm_terminal.Name = "bfr_rearm_terminal"
bfr_rearm_terminal.Page += 3 -> _OrderTerminalDefinition.EquipmentPage(Map.empty[String, ()=>Equipment]) //TODO add stock to page
bfr_rearm_terminal.Page += 4 -> _OrderTerminalDefinition.VehicleLoadoutPage()
bfr_rearm_terminal.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(Map.empty[String, ()=>Equipment]) //TODO add stock to page
bfr_rearm_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
bfr_rearm_terminal.SellEquipmentByDefault = true //TODO ?
air_rearm_terminal.Name = "air_rearm_terminal"
air_rearm_terminal.Page += 3 -> _OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
air_rearm_terminal.Page += 4 -> _OrderTerminalDefinition.VehicleLoadoutPage()
air_rearm_terminal.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
air_rearm_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
air_rearm_terminal.SellEquipmentByDefault = true //TODO ?
ground_rearm_terminal.Name = "ground_rearm_terminal"
ground_rearm_terminal.Page += 3 -> _OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
ground_rearm_terminal.Page += 4 -> _OrderTerminalDefinition.VehicleLoadoutPage()
ground_rearm_terminal.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
ground_rearm_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
ground_rearm_terminal.SellEquipmentByDefault = true //TODO ?
manned_turret.Name = "manned_turret"
manned_turret.MaxHealth = 3600

View file

@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
import net.psforever.objects.equipment.EquipmentSize
import net.psforever.objects.equipment.{EquipmentSize, EquipmentSlot}
/**
* A size-checked unit of storage (or mounting) for `Equipment`.

View file

@ -1,8 +1,8 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
import net.psforever.objects.definition.AvatarDefinition
import net.psforever.objects.equipment.{Equipment, EquipmentSize}
import net.psforever.objects.definition.{AvatarDefinition, ExoSuitDefinition, SpecialExoSuitDefinition}
import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot}
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem}
import net.psforever.objects.loadouts.Loadout
import net.psforever.objects.serverobject.affinity.FactionAffinity
@ -10,6 +10,7 @@ import net.psforever.objects.vital.resistance.ResistanceProfile
import net.psforever.objects.vital.{DamageResistanceModel, Vitality}
import net.psforever.objects.zones.ZoneAware
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate.{Cosmetics, DetailedCharacterData, PersonalStyle}
import net.psforever.types._
import scala.annotation.tailrec
@ -29,18 +30,18 @@ class Player(private val core : Avatar) extends PlanetSideGameObject
private var maxHealth : Int = 100 //TODO affected by empire benefits, territory benefits, and bops
private var maxStamina : Int = 100 //does anything affect this?
private var exosuit : ExoSuitDefinition = ExoSuitDefinition.Standard
private var exosuit : ExoSuitDefinition = GlobalDefinitions.Standard
private val freeHand : EquipmentSlot = new OffhandEquipmentSlot(EquipmentSize.Inventory)
private val holsters : Array[EquipmentSlot] = Array.fill[EquipmentSlot](5)(new EquipmentSlot)
private val inventory : GridInventory = GridInventory()
private var drawnSlot : Int = Player.HandsDownSlot
private var lastDrawnSlot : Int = Player.HandsDownSlot
private var backpackAccess : Option[PlanetSideGUID] = None
private var facingYawUpper : Float = 0f
private var crouching : Boolean = false
private var jumping : Boolean = false
private var cloaked : Boolean = false
private var backpackAccess : Option[PlanetSideGUID] = None
private var vehicleSeated : Option[PlanetSideGUID] = None
private var vehicleOwned : Option[PlanetSideGUID] = None
@ -347,7 +348,71 @@ class Player(private val core : Avatar) extends PlanetSideGameObject
Cloaked
}
private var usingSpecial : (SpecialExoSuitDefinition.Mode.Value)=>SpecialExoSuitDefinition.Mode.Value = DefaultUsingSpecial
def PersonalStyleFeatures : Option[Cosmetics] = core.PersonalStyleFeatures
def AddToPersonalStyle(value : PersonalStyle.Value) : (Option[Cosmetics], Option[Cosmetics]) = {
val original = core.PersonalStyleFeatures
if(DetailedCharacterData.isBR24(core.BEP)) {
core.PersonalStyleFeatures = original match {
case Some(cosmetic) =>
cosmetic + value
case None =>
Cosmetics(value)
}
(original, core.PersonalStyleFeatures)
}
else {
(None, None)
}
}
def RemoveFromPersonalStyle(value : PersonalStyle.Value) : (Option[Cosmetics], Option[Cosmetics]) = {
val original = core.PersonalStyleFeatures
original match {
case Some(cosmetics) =>
(original, core.PersonalStyleFeatures = cosmetics - value)
case None =>
(None, None)
}
}
private def BasicFeatureToggle(feature : PersonalStyle.Value) : (Option[Cosmetics], Option[Cosmetics]) = core.PersonalStyleFeatures match {
case Some(c : Cosmetics) =>
if(c.Styles.contains(feature)) {
RemoveFromPersonalStyle(feature)
}
else {
AddToPersonalStyle(feature)
}
case None =>
AddToPersonalStyle(feature)
}
def ToggleHelmet : (Option[Cosmetics], Option[Cosmetics]) = BasicFeatureToggle(PersonalStyle.NoHelmet)
def ToggleShades : (Option[Cosmetics], Option[Cosmetics]) = BasicFeatureToggle(PersonalStyle.Sunglasses)
def ToggleEarpiece : (Option[Cosmetics], Option[Cosmetics]) = BasicFeatureToggle(PersonalStyle.Earpiece)
def ToggleHat : (Option[Cosmetics], Option[Cosmetics]) = {
core.PersonalStyleFeatures match {
case Some(c : Cosmetics) =>
if(c.Styles.contains(PersonalStyle.BrimmedCap)) {
(RemoveFromPersonalStyle(PersonalStyle.BrimmedCap)._1,
AddToPersonalStyle(PersonalStyle.Beret)._2)
}
else if(c.Styles.contains(PersonalStyle.Beret)) {
RemoveFromPersonalStyle(PersonalStyle.Beret)
}
else {
AddToPersonalStyle(PersonalStyle.BrimmedCap)
}
case None =>
AddToPersonalStyle(PersonalStyle.BrimmedCap)
}
}
private var usingSpecial : SpecialExoSuitDefinition.Mode.Value=>SpecialExoSuitDefinition.Mode.Value = DefaultUsingSpecial
private var gettingSpecial : ()=>SpecialExoSuitDefinition.Mode.Value = DefaultGettingSpecial

View file

@ -3,7 +3,7 @@ package net.psforever.objects
import akka.actor.ActorRef
import net.psforever.objects.definition.VehicleDefinition
import net.psforever.objects.equipment.{Equipment, EquipmentSize}
import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot}
import net.psforever.objects.inventory.{Container, GridInventory, InventoryTile}
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.PlanetSideServerObject

View file

@ -1,24 +1,27 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
package net.psforever.objects.definition
import net.psforever.objects.GlobalDefinitions
import net.psforever.objects.equipment.EquipmentSize
import net.psforever.objects.inventory.InventoryTile
import net.psforever.objects.vital._
import net.psforever.objects.vital.resistance.ResistanceProfileMutators
import net.psforever.types.ExoSuitType
import net.psforever.types.{CertificationType, ExoSuitType}
/**
* A definition for producing the personal armor the player wears.
* Players are influenced by the exo-suit they wear in a variety of ways, with speed and available equipment slots being major differences.
* @param suitType the `Enumeration` corresponding to this exo-suit
*/
class ExoSuitDefinition(private val suitType : ExoSuitType.Value) extends ResistanceProfileMutators
class ExoSuitDefinition(private val suitType : ExoSuitType.Value) extends BasicDefinition
with ResistanceProfileMutators
with DamageResistanceModel {
protected var permission : Int = 0 //TODO certification type?
protected var permissions : List[CertificationType.Value] = List.empty
protected var maxArmor : Int = 0
protected val holsters : Array[EquipmentSize.Value] = Array.fill[EquipmentSize.Value](5)(EquipmentSize.Blocked)
protected var inventoryScale : InventoryTile = InventoryTile.Tile11 //override with custom InventoryTile
protected var inventoryOffset : Int = 0
Name = "exo-suit"
Damage = StandardInfantryDamage
Resistance = StandardInfantryResistance
Model = StandardResolutions.Infantry
@ -67,10 +70,19 @@ class ExoSuitDefinition(private val suitType : ExoSuitType.Value) extends Resist
}
}
def Permissions : List[CertificationType.Value] = permissions
def Permissions_=(certs : List[CertificationType.Value]) : List[CertificationType.Value] = {
permissions = certs
Permissions
}
def Use : ExoSuitDefinition = this
}
class SpecialExoSuitDefinition(private val suitType : ExoSuitType.Value) extends ExoSuitDefinition(suitType) {
Name = "heavy_armor"
private var activatedSpecial : SpecialExoSuitDefinition.Mode.Value = SpecialExoSuitDefinition.Mode.Normal
def UsingSpecial : SpecialExoSuitDefinition.Mode.Value = activatedSpecial
@ -118,65 +130,6 @@ object SpecialExoSuitDefinition {
}
object ExoSuitDefinition {
final val Standard = ExoSuitDefinition(ExoSuitType.Standard)
Standard.MaxArmor = 50
Standard.InventoryScale = InventoryTile.Tile96
Standard.InventoryOffset = 6
Standard.Holster(0, EquipmentSize.Pistol)
Standard.Holster(2, EquipmentSize.Rifle)
Standard.Holster(4, EquipmentSize.Melee)
Standard.ResistanceDirectHit = 4
Standard.ResistanceSplash = 15
Standard.ResistanceAggravated = 8
final val Agile = ExoSuitDefinition(ExoSuitType.Agile)
Agile.MaxArmor = 100
Agile.InventoryScale = InventoryTile.Tile99
Agile.InventoryOffset = 6
Agile.Holster(0, EquipmentSize.Pistol)
Agile.Holster(1, EquipmentSize.Pistol)
Agile.Holster(2, EquipmentSize.Rifle)
Agile.Holster(4, EquipmentSize.Melee)
Agile.ResistanceDirectHit = 6
Agile.ResistanceSplash = 25
Agile.ResistanceAggravated = 10
final val Reinforced = ExoSuitDefinition(ExoSuitType.Reinforced)
Reinforced.permission = 1
Reinforced.MaxArmor = 200
Reinforced.InventoryScale = InventoryTile.Tile1209
Reinforced.InventoryOffset = 6
Reinforced.Holster(0, EquipmentSize.Pistol)
Reinforced.Holster(1, EquipmentSize.Pistol)
Reinforced.Holster(2, EquipmentSize.Rifle)
Reinforced.Holster(3, EquipmentSize.Rifle)
Reinforced.Holster(4, EquipmentSize.Melee)
Reinforced.ResistanceDirectHit = 10
Reinforced.ResistanceSplash = 35
Reinforced.ResistanceAggravated = 12
final val Infiltration = ExoSuitDefinition(ExoSuitType.Infiltration)
Infiltration.permission = 1
Infiltration.MaxArmor = 0
Infiltration.InventoryScale = InventoryTile.Tile66
Infiltration.InventoryOffset = 6
Infiltration.Holster(0, EquipmentSize.Pistol)
Infiltration.Holster(4, EquipmentSize.Melee)
final val MAX = SpecialExoSuitDefinition(ExoSuitType.MAX)
MAX.permission = 1
MAX.MaxArmor = 650
MAX.InventoryScale = InventoryTile.Tile1612
MAX.InventoryOffset = 6
MAX.Holster(0, EquipmentSize.Max)
MAX.Holster(4, EquipmentSize.Melee)
MAX.Subtract.Damage1 = -2
MAX.ResistanceDirectHit = 6
MAX.ResistanceSplash = 35
MAX.ResistanceAggravated = 10
MAX.Damage = StandardMaxDamage
MAX.Model = StandardResolutions.Max
def apply(suitType : ExoSuitType.Value) : ExoSuitDefinition = {
new ExoSuitDefinition(suitType)
}
@ -188,11 +141,11 @@ object ExoSuitDefinition {
*/
def Select(suit : ExoSuitType.Value) : ExoSuitDefinition = {
suit match {
case ExoSuitType.Agile => ExoSuitDefinition.Agile.Use
case ExoSuitType.Infiltration => ExoSuitDefinition.Infiltration.Use
case ExoSuitType.MAX => ExoSuitDefinition.MAX.Use
case ExoSuitType.Reinforced => ExoSuitDefinition.Reinforced.Use
case _ => ExoSuitDefinition.Standard.Use
case ExoSuitType.Agile => GlobalDefinitions.Agile.Use
case ExoSuitType.Infiltration => GlobalDefinitions.Infiltration.Use
case ExoSuitType.MAX => GlobalDefinitions.MAX.Use
case ExoSuitType.Reinforced => GlobalDefinitions.Reinforced.Use
case _ => GlobalDefinitions.Standard.Use
}
}
}

View file

@ -2,16 +2,45 @@
package net.psforever.objects.definition.converter
import net.psforever.objects.ConstructionItem
import net.psforever.packet.game.objectcreate.{ACEData, DetailedACEData}
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate.{CommonFieldData, DetailedConstructionToolData, HandheldData}
import scala.util.{Success, Try}
class ACEConverter extends ObjectCreateConverter[ConstructionItem]() {
override def ConstructorData(obj : ConstructionItem) : Try[ACEData] = {
Success(ACEData(0,0))
override def ConstructorData(obj : ConstructionItem) : Try[HandheldData] = {
Success(
HandheldData(
CommonFieldData(
obj.Faction,
false,
false,
true,
None,
false,
None,
None,
PlanetSideGUID(0)
)
)
)
}
override def DetailedConstructorData(obj : ConstructionItem) : Try[DetailedACEData] = {
Success(DetailedACEData(0))
override def DetailedConstructorData(obj : ConstructionItem) : Try[DetailedConstructionToolData] = {
Success(
DetailedConstructionToolData(
CommonFieldData(
obj.Faction,
false,
false,
true,
None,
false,
None,
None,
PlanetSideGUID(0)
)
)
)
}
}

View file

@ -2,16 +2,33 @@
package net.psforever.objects.definition.converter
import net.psforever.objects.AmmoBox
import net.psforever.packet.game.objectcreate.{AmmoBoxData, DetailedAmmoBoxData}
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate.{CommonFieldData, DetailedAmmoBoxData}
import net.psforever.types.PlanetSideEmpire
import scala.util.{Success, Try}
class AmmoBoxConverter extends ObjectCreateConverter[AmmoBox] {
override def ConstructorData(obj : AmmoBox) : Try[AmmoBoxData] = {
Success(AmmoBoxData())
override def ConstructorData(obj : AmmoBox) : Try[CommonFieldData] = {
Success(CommonFieldData()(false))
}
override def DetailedConstructorData(obj : AmmoBox) : Try[DetailedAmmoBoxData] = {
Success(DetailedAmmoBoxData(8, obj.Capacity))
Success(
DetailedAmmoBoxData(
CommonFieldData(
PlanetSideEmpire.NEUTRAL,
bops = false,
alternate = false,
true,
None,
false,
None,
None,
PlanetSideGUID(0)
),
obj.Capacity
)
)
}
}

View file

@ -1,8 +1,9 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.definition.converter
import net.psforever.objects.{EquipmentSlot, Player}
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.Player
import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate._
import net.psforever.types.{ExoSuitType, GrenadeState, ImplantType}
@ -63,18 +64,22 @@ object AvatarConverter {
* @param obj the `Player` game object
* @return the resulting `CharacterAppearanceData`
*/
def MakeAppearanceData(obj : Player) : (Int)=>CharacterAppearanceData = {
def MakeAppearanceData(obj : Player) : Int=>CharacterAppearanceData = {
val alt_model_flag : Boolean = obj.isBackpack
val aa : Int=>CharacterAppearanceA = CharacterAppearanceA(
BasicCharacterData(obj.Name, obj.Faction, obj.Sex, obj.Head, obj.Voice),
black_ops = false,
alt_model_flag,
false,
None,
jammered = false,
CommonFieldData(
obj.Faction,
bops = false,
alt_model_flag,
false,
None,
false,
None,
v5 = None,
PlanetSideGUID(0)
),
obj.ExoSuit,
None,
0,
0,
0L,
0,
@ -108,22 +113,22 @@ object AvatarConverter {
def MakeCharacterData(obj : Player) : (Boolean,Boolean)=>CharacterData = {
val MaxArmor = obj.MaxArmor
CharacterData(
255 * obj.Health / obj.MaxHealth,
StatConverter.Health(obj.Health, obj.MaxHealth),
if(MaxArmor == 0) {
0
}
else {
255 * obj.Armor / MaxArmor
StatConverter.Health(obj.Armor, MaxArmor)
},
DressBattleRank(obj),
0,
DressCommandRank(obj),
MakeImplantEffectList(obj.Implants),
MakeCosmetics(obj.BEP)
MakeCosmetics(obj)
)
}
def MakeDetailedCharacterData(obj : Player) : (Option[Int])=>DetailedCharacterData = {
def MakeDetailedCharacterData(obj : Player) : Option[Int]=>DetailedCharacterData = {
val bep : Long = obj.BEP
val maxOpt : Option[Long] = if(obj.ExoSuit == ExoSuitType.MAX) { Some(0L) } else { None }
val ba : DetailedCharacterA = DetailedCharacterA(
@ -149,9 +154,9 @@ object AvatarConverter {
0L, 0L, 0L, 0L, 0L,
Some(DCDExtra2(0, 0)),
Nil, Nil, false,
MakeCosmetics(bep)
MakeCosmetics(obj)
)
(pad_length : Option[Int]) => DetailedCharacterData(ba, bb(bep, pad_length))(pad_length)
pad_length : Option[Int] => DetailedCharacterData(ba, bb(bep, pad_length))(pad_length)
}
def MakeInventoryData(obj : Player) : InventoryData = {
@ -219,8 +224,8 @@ object AvatarConverter {
* @see `ImplantEntry` in `DetailedCharacterData`
*/
private def MakeImplantEntries(obj : Player) : List[ImplantEntry] = {
val numImplants : Int = DetailedCharacterData.numberOfImplantSlots(obj.BEP)
val implants = obj.Implants
//val numImplants : Int = DetailedCharacterData.numberOfImplantSlots(obj.BEP)
//val implants = obj.Implants
obj.Implants.map({ case(implant, initialization, _) =>
if(initialization == 0) {
ImplantEntry(implant, None)
@ -238,7 +243,7 @@ object AvatarConverter {
*/
private def MakeImplantEffectList(implants : Seq[(ImplantType.Value, Long, Boolean)]) : List[ImplantEffects.Value] = {
implants.collect {
case ((implant,_,true)) =>
case (implant,_,true) =>
implant match {
case ImplantType.AdvancedRegen =>
ImplantEffects.RegenEffects
@ -253,14 +258,16 @@ object AvatarConverter {
}
/**
* Should this player be of battle rank 24 or higher, they will have a mandatory cosmetics object.
* @param bep battle experience points
* Should this player be of battle rank 24 or higher, they will have a mandatory cosmetics object in their bitstream.
* Players that have not yet set any cosmetic personal effects will still have this field recorded as `None`
* but it must be represented nonetheless.
* @param obj the `Player` game object
* @see `Cosmetics`
* @return the `Cosmetics` options
*/
def MakeCosmetics(bep : Long) : Option[Cosmetics] =
if(DetailedCharacterData.isBR24(bep)) {
Some(Cosmetics(false, false, false, false, false))
def MakeCosmetics(obj : Player) : Option[Cosmetics] =
if(DetailedCharacterData.isBR24(obj.BEP)) {
obj.PersonalStyleFeatures.orElse(Some(Cosmetics()))
}
else {
None
@ -290,7 +297,7 @@ object AvatarConverter {
* @param builder the function used to transform to the decoded packet form
* @return a list of all items that were in the holsters in decoded packet form
*/
private def MakeHolsters(obj : Player, builder : ((Int, Equipment) => InternalSlot)) : List[InternalSlot] = {
private def MakeHolsters(obj : Player, builder : (Int, Equipment) => InternalSlot) : List[InternalSlot] = {
recursiveMakeHolsters(obj.Holsters().iterator, builder)
}
@ -338,7 +345,7 @@ object AvatarConverter {
* @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) {
list
}

View file

@ -2,16 +2,20 @@
package net.psforever.objects.definition.converter
import net.psforever.objects.SimpleItem
import net.psforever.packet.game.objectcreate.{BoomerTriggerData, DetailedBoomerTriggerData}
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate.{CommonFieldData, DetailedConstructionToolData, HandheldData}
import net.psforever.types.PlanetSideEmpire
import scala.util.{Success, Try}
class BoomerTriggerConverter extends ObjectCreateConverter[SimpleItem]() {
override def ConstructorData(obj : SimpleItem) : Try[BoomerTriggerData] = {
Success(BoomerTriggerData())
override def ConstructorData(obj : SimpleItem) : Try[HandheldData] = {
Success(HandheldData(CommonFieldData()))
}
override def DetailedConstructorData(obj : SimpleItem) : Try[DetailedBoomerTriggerData] = {
Success(DetailedBoomerTriggerData())
override def DetailedConstructorData(obj : SimpleItem) : Try[DetailedConstructionToolData] = {
Success(DetailedConstructionToolData(
CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, None, None, PlanetSideGUID(0))
))
}
}

View file

@ -1,8 +1,9 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.definition.converter
import net.psforever.objects.{EquipmentSlot, Player}
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.Player
import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate._
import net.psforever.types._
@ -35,17 +36,21 @@ class CharacterSelectConverter extends AvatarConverter {
* @see `AvatarConverter.MakeAppearanceData`
* @return the resulting `CharacterAppearanceData`
*/
private def MakeAppearanceData(obj : Player) : (Int)=>CharacterAppearanceData = {
private def MakeAppearanceData(obj : Player) : Int=>CharacterAppearanceData = {
val aa : Int=>CharacterAppearanceA = CharacterAppearanceA(
BasicCharacterData(obj.Name, obj.Faction, obj.Sex, obj.Head, CharacterVoice.Mute),
black_ops = false,
false,
false,
None,
jammered = false,
CommonFieldData(
obj.Faction,
bops = false,
false,
false,
None,
false,
None,
v5 = None,
PlanetSideGUID(0)
),
obj.ExoSuit,
None,
0,
0,
0L,
0,
@ -76,7 +81,7 @@ class CharacterSelectConverter extends AvatarConverter {
CharacterAppearanceData(aa, ab, RibbonBars())
}
private def MakeDetailedCharacterData(obj : Player) : (Option[Int]=>DetailedCharacterData) = {
private def MakeDetailedCharacterData(obj : Player) : Option[Int]=>DetailedCharacterData = {
val bep : Long = obj.BEP
val maxOpt : Option[Long] = if(obj.ExoSuit == ExoSuitType.MAX) { Some(0L) } else { None }
val ba : DetailedCharacterA = DetailedCharacterA(
@ -102,9 +107,9 @@ class CharacterSelectConverter extends AvatarConverter {
0L, 0L, 0L, 0L, 0L,
Some(DCDExtra2(0, 0)),
Nil, Nil, false,
AvatarConverter.MakeCosmetics(bep)
AvatarConverter.MakeCosmetics(obj)
)
(pad_length : Option[Int]) => DetailedCharacterData(ba, bb(bep, pad_length))(pad_length)
pad_length : Option[Int] => DetailedCharacterData(ba, bb(bep, pad_length))(pad_length)
}
/**

View file

@ -2,16 +2,46 @@
package net.psforever.objects.definition.converter
import net.psforever.objects.SimpleItem
import net.psforever.packet.game.objectcreate.{CommandDetonaterData, DetailedCommandDetonaterData}
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate.{CommonFieldData, DetailedCommandDetonaterData, HandheldData}
import net.psforever.types.PlanetSideEmpire
import scala.util.{Success, Try}
class CommandDetonaterConverter extends ObjectCreateConverter[SimpleItem]() {
override def ConstructorData(obj : SimpleItem) : Try[CommandDetonaterData] = {
Success(CommandDetonaterData())
override def ConstructorData(obj : SimpleItem) : Try[HandheldData] = {
Success(
HandheldData(
CommonFieldData(
obj.Faction,
false,
false,
false,
None,
false,
None,
None,
PlanetSideGUID(0)
)
)
)
}
override def DetailedConstructorData(obj : SimpleItem) : Try[DetailedCommandDetonaterData] = {
Success(DetailedCommandDetonaterData())
Success(
DetailedCommandDetonaterData(
CommonFieldData(
obj.Faction,
false,
false,
false,
None,
false,
None,
None,
PlanetSideGUID(0)
)
)
)
}
}

View file

@ -1,8 +1,9 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.definition.converter
import net.psforever.objects.{EquipmentSlot, Player}
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.Player
import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate._
import net.psforever.types._
@ -30,17 +31,21 @@ class CorpseConverter extends AvatarConverter {
* @param obj the `Player` game object
* @return the resulting `CharacterAppearanceData`
*/
private def MakeAppearanceData(obj : Player) : (Int)=>CharacterAppearanceData = {
private def MakeAppearanceData(obj : Player) : Int=>CharacterAppearanceData = {
val aa : Int=>CharacterAppearanceA = CharacterAppearanceA(
BasicCharacterData(obj.Name, obj.Faction, CharacterGender.Male, 0, CharacterVoice.Mute),
black_ops = false,
altModel = true,
false,
None,
jammered = false,
CommonFieldData(
obj.Faction,
bops = false,
alternate = true,
false,
None,
false,
None,
v5 = None,
PlanetSideGUID(0)
),
obj.ExoSuit,
None,
0,
0,
0L,
0,
@ -71,7 +76,7 @@ class CorpseConverter extends AvatarConverter {
CharacterAppearanceData(aa, ab, RibbonBars())
}
private def MakeDetailedCharacterData(obj : Player) : (Option[Int]=>DetailedCharacterData) = {
private def MakeDetailedCharacterData(obj : Player) : Option[Int]=>DetailedCharacterData = {
val maxOpt : Option[Long] = if(obj.ExoSuit == ExoSuitType.MAX) { Some(0L) } else { None }
val ba : DetailedCharacterA = DetailedCharacterA(
bep = 0L,

View file

@ -11,22 +11,26 @@ import scala.util.{Failure, Success, Try}
class FieldTurretConverter extends ObjectCreateConverter[TurretDeployable]() {
override def ConstructorData(obj : TurretDeployable) : Try[OneMannedFieldTurretData] = {
val health = 255 * obj.Health / obj.MaxHealth //TODO not precise
val health = StatConverter.Health(obj.Health, obj.MaxHealth)
if(health > 3) {
Success(
OneMannedFieldTurretData(
SmallDeployableData(
CommonFieldDataWithPlacement(
PlacementData(obj.Position, obj.Orientation),
obj.Faction,
bops = false,
destroyed = false,
unk1 = 0,
obj.Jammered,
unk2 = false,
obj.Owner match {
case Some(owner) => owner
case None => PlanetSideGUID(0)
}
CommonFieldData(
obj.Faction,
bops = false,
alternate = false,
true,
None,
false,
Some(false),
None,
obj.Owner match {
case Some(owner) => owner
case None => PlanetSideGUID(0)
}
)
),
health,
Some(InventoryData(FieldTurretConverter.MakeMountings(obj)))
@ -36,18 +40,21 @@ class FieldTurretConverter extends ObjectCreateConverter[TurretDeployable]() {
else {
Success(
OneMannedFieldTurretData(
SmallDeployableData(
CommonFieldDataWithPlacement(
PlacementData(obj.Position, obj.Orientation),
obj.Faction,
bops = false,
destroyed = true,
unk1 = 0,
jammered = false,
unk2 = false,
owner_guid = PlanetSideGUID(0)
CommonFieldData(
obj.Faction,
bops = false,
alternate = true,
true,
None,
false,
Some(false),
None,
PlanetSideGUID(0)
)
),
0,
None
0
)
)
}
@ -60,7 +67,7 @@ class FieldTurretConverter extends ObjectCreateConverter[TurretDeployable]() {
object FieldTurretConverter {
private def MakeMountings(obj : WeaponTurret) : List[InventoryItemData.InventoryItem] = {
obj.Weapons.map({
case((index, slot)) =>
case(index, slot) =>
val equip : Equipment = slot.Equipment.get
val equipDef = equip.Definition
InventoryItemData(equipDef.ObjectId, equip.GUID, index, equipDef.Packet.ConstructorData(equip).get)

View file

@ -1,15 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.definition.converter
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.packet.game.objectcreate.CommonTerminalData
import scala.util.{Failure, Success, Try}
class ImplantTerminalInterfaceConverter extends ObjectCreateConverter[Terminal]() {
override def DetailedConstructorData(obj : Terminal) : Try[CommonTerminalData] =
Failure(new Exception("ImplantTerminalInterfaceConverter should not be used to generate detailed CommonTerminalData"))
override def ConstructorData(obj : Terminal) : Try[CommonTerminalData] =
Success(CommonTerminalData(net.psforever.types.PlanetSideEmpire.VS)) //TODO shortcut
}

View file

@ -3,12 +3,39 @@ package net.psforever.objects.definition.converter
import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.ce.TelepadLike
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate._
import net.psforever.types.PlanetSideEmpire
import scala.util.{Success, Try}
import scala.util.{Failure, Success, Try}
class InternalTelepadDeployableConverter extends ObjectCreateConverter[PlanetSideGameObject with TelepadLike]() {
override def ConstructorData(obj : PlanetSideGameObject with TelepadLike) : Try[ContainedTelepadDeployableData] = {
Success(ContainedTelepadDeployableData(101, obj.Router.get))
override def ConstructorData(obj : PlanetSideGameObject with TelepadLike) : Try[TelepadDeployableData] = {
obj.Router match {
case Some(PlanetSideGUID(0)) =>
Failure(new IllegalStateException("InternalTelepadDeployableConverter: knowledge of parent Router is null"))
case Some(router) =>
Success(
TelepadDeployableData(
CommonFieldData(
PlanetSideEmpire.NEUTRAL,
bops = false,
alternate = false,
true,
None,
false,
None,
Some(router.guid),
PlanetSideGUID(0)
),
unk1 = 128,
unk2 = 0
)
)
case None =>
Failure(new IllegalStateException("InternalTelepadDeployableConverter: telepad needs to know id of its parent Router"))
}
}
}

View file

@ -2,13 +2,13 @@
package net.psforever.objects.definition.converter
import net.psforever.objects.Kit
import net.psforever.packet.game.objectcreate.{AmmoBoxData, DetailedAmmoBoxData}
import net.psforever.packet.game.objectcreate.{CommonFieldData, DetailedAmmoBoxData}
import scala.util.{Success, Try}
class KitConverter extends ObjectCreateConverter[Kit]() {
override def ConstructorData(obj : Kit) : Try[AmmoBoxData] = {
Success(AmmoBoxData())
override def ConstructorData(obj : Kit) : Try[CommonFieldData] = {
Success(CommonFieldData()(false))
}
override def DetailedConstructorData(obj : Kit) : Try[DetailedAmmoBoxData] = {

View file

@ -4,21 +4,34 @@ package net.psforever.objects.definition.converter
import net.psforever.objects.LockerContainer
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.inventory.GridInventory
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate._
import net.psforever.types.PlanetSideEmpire
import scala.util.{Success, Try}
class LockerContainerConverter extends ObjectCreateConverter[LockerContainer]() {
override def ConstructorData(obj : LockerContainer) : Try[LockerContainerData] = {
Success(LockerContainerData(InventoryData(MakeInventory(obj.Inventory))))
MakeInventory(obj.Inventory) match {
case Nil =>
Success(LockerContainerData(None))
case list =>
Success(LockerContainerData(InventoryData(list)))
}
}
override def DetailedConstructorData(obj : LockerContainer) : Try[DetailedLockerContainerData] = {
if(obj.Inventory.Size > 0) {
Success(DetailedLockerContainerData(8, Some(InventoryData(MakeDetailedInventory(obj.Inventory)))))
Success(DetailedLockerContainerData(
CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, None, None, PlanetSideGUID(0)),
Some(InventoryData(MakeDetailedInventory(obj.Inventory)))
))
}
else {
Success(DetailedLockerContainerData(8, None))
Success(DetailedLockerContainerData(
CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, None, None, PlanetSideGUID(0)),
None
))
}
}

View file

@ -2,16 +2,45 @@
package net.psforever.objects.definition.converter
import net.psforever.objects.SimpleItem
import net.psforever.packet.game.objectcreate.{DetailedREKData, REKData}
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate.{CommonFieldData, DetailedREKData, REKData}
import net.psforever.types.PlanetSideEmpire
import scala.util.{Success, Try}
class REKConverter extends ObjectCreateConverter[SimpleItem]() {
override def ConstructorData(obj : SimpleItem) : Try[REKData] = {
Success(REKData(8,0))
Success(
REKData(
CommonFieldData(
PlanetSideEmpire.NEUTRAL, //TODO faction affinity
false,
false,
true,
None,
false,
Some(false),
None,
PlanetSideGUID(0))
)
)
}
override def DetailedConstructorData(obj : SimpleItem) : Try[DetailedREKData] = {
Success(DetailedREKData(8))
Success(
DetailedREKData(
CommonFieldData(
PlanetSideEmpire.NEUTRAL, //TODO faction affinity
false,
false,
true,
None,
false,
Some(false),
None,
PlanetSideGUID(0)
)
)
)
}
}
}

View file

@ -9,21 +9,26 @@ import scala.util.{Failure, Success, Try}
class ShieldGeneratorConverter extends ObjectCreateConverter[ShieldGeneratorDeployable]() {
override def ConstructorData(obj : ShieldGeneratorDeployable) : Try[AegisShieldGeneratorData] = {
val health = 255 * obj.Health / obj.MaxHealth //TODO not precise
val health = StatConverter.Health(obj.Health, obj.MaxHealth)
if(health > 0) {
Success(
AegisShieldGeneratorData(
CommonFieldData(
CommonFieldDataWithPlacement(
PlacementData(obj.Position, obj.Orientation),
obj.Faction,
bops = false,
destroyed = false,
unk = 0,
jammered = false,
obj.Owner match {
case Some(owner) => owner
case None => PlanetSideGUID(0)
}
CommonFieldData(
obj.Faction,
bops = false,
alternate = false,
v1 = false,
v2 = None,
v3 = false,
None,
None,
obj.Owner match {
case Some(owner) => owner
case None => PlanetSideGUID(0)
}
)
),
health
)
@ -32,14 +37,19 @@ class ShieldGeneratorConverter extends ObjectCreateConverter[ShieldGeneratorDepl
else {
Success(
AegisShieldGeneratorData(
CommonFieldData(
CommonFieldDataWithPlacement(
PlacementData(obj.Position, obj.Orientation),
obj.Faction,
bops = false,
destroyed = true,
unk = 0,
jammered = false,
player_guid = PlanetSideGUID(0)
CommonFieldData(
obj.Faction,
bops = false,
alternate = true,
v1 = false,
v2 = None,
v3 = false,
None,
None,
PlanetSideGUID(0)
)
),
0
)

View file

@ -4,29 +4,33 @@ package net.psforever.objects.definition.converter
import net.psforever.objects.ce.Deployable
import net.psforever.objects.PlanetSideGameObject
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate.{PlacementData, SmallDeployableData}
import net.psforever.packet.game.objectcreate._
import scala.util.{Failure, Success, Try}
class SmallDeployableConverter extends ObjectCreateConverter[PlanetSideGameObject with Deployable]() {
override def ConstructorData(obj : PlanetSideGameObject with Deployable) : Try[SmallDeployableData] = {
override def ConstructorData(obj : PlanetSideGameObject with Deployable) : Try[CommonFieldDataWithPlacement] = {
Success(
SmallDeployableData(
CommonFieldDataWithPlacement(
PlacementData(obj.Position, obj.Orientation),
obj.Faction,
bops = false,
destroyed = false,
unk1 = 0,
jammered = false,
unk2 = false,
obj.Owner match {
case Some(owner) => owner
case None => PlanetSideGUID(0)
}
CommonFieldData(
obj.Faction,
false,
false,
false,
None,
false,
Some(false),
None,
obj.Owner match {
case Some(owner) => owner
case None => PlanetSideGUID(0)
}
)
)
)
}
override def DetailedConstructorData(obj : PlanetSideGameObject with Deployable) : Try[SmallDeployableData] =
Failure(new Exception("converter should not be used to generate detailed SmallDeployableData"))
override def DetailedConstructorData(obj : PlanetSideGameObject with Deployable) : Try[CommonFieldDataWithPlacement] =
Failure(new Exception("converter should not be used to generate detailed small deployable data"))
}

View file

@ -11,22 +11,26 @@ import scala.util.{Failure, Success, Try}
class SmallTurretConverter extends ObjectCreateConverter[TurretDeployable]() {
override def ConstructorData(obj : TurretDeployable) : Try[SmallTurretData] = {
val health = 255 * obj.Health / obj.MaxHealth //TODO not precise
val health = StatConverter.Health(obj.Health, obj.MaxHealth)
if(health > 0) {
Success(
SmallTurretData(
SmallDeployableData(
CommonFieldDataWithPlacement(
PlacementData(obj.Position, obj.Orientation),
obj.Faction,
bops = false,
destroyed = false,
unk1 = 0,
obj.Jammered,
unk2 = false,
obj.Owner match {
case Some(owner) => owner
case None => PlanetSideGUID(0)
}
CommonFieldData(
obj.Faction,
bops = false,
alternate = false,
false,
None,
false,
Some(true),
None,
obj.Owner match {
case Some(owner) => owner
case None => PlanetSideGUID(0)
}
)
),
health,
Some(InventoryData(SmallTurretConverter.MakeMountings(obj)))
@ -36,18 +40,21 @@ class SmallTurretConverter extends ObjectCreateConverter[TurretDeployable]() {
else {
Success(
SmallTurretData(
SmallDeployableData(
CommonFieldDataWithPlacement(
PlacementData(obj.Position, obj.Orientation),
obj.Faction,
bops = false,
destroyed = true,
unk1 = 0,
jammered = false,
unk2 = false,
owner_guid = PlanetSideGUID(0)
CommonFieldData(
obj.Faction,
bops = false,
alternate = true,
false,
None,
false,
Some(false),
None,
PlanetSideGUID(0)
)
),
0,
None
0
)
)
}

View file

@ -2,10 +2,10 @@
package net.psforever.objects.definition.converter
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.packet.game.objectcreate.CommonTerminalData
import net.psforever.packet.game.objectcreate.CommonFieldData
import scala.util.{Success, Try}
class SpawnTubeConverter extends ObjectCreateConverter[SpawnTube]() {
override def ConstructorData(obj : SpawnTube) : Try[CommonTerminalData] = { Success(CommonTerminalData(obj.Faction)) }
override def ConstructorData(obj : SpawnTube) : Try[CommonFieldData] = { Success(CommonFieldData(obj.Faction)(false)) }
}

View file

@ -0,0 +1,20 @@
package net.psforever.objects.definition.converter
object StatConverter {
/**
* Takes a measure of a value against the maximum possible value and
* transforms it to a scaled number that can be written within a specific domain.<br>
* <br>
* The default (and absolutely common) situation writes a scaled number that can be represented by an unsigned `8u`.
* The maximum value is 255, or 2^8^ - 1.
* The minimum value is 0;
* but, due to how game models are represented at various health,
* the representable minimum value is allowed to plateau at 3.
* Any result less than 3 creates the same situation as if the result were 0.
*/
def Health(health : Int, maxHealth : Int, min : Int = 3, max : Int = 255) : Int =
if(health < 1) 0
else if(health <= min || min >= max) min
else if(health >= maxHealth) max
else math.floor(max * health / maxHealth).toInt
}

View file

@ -9,22 +9,26 @@ import scala.util.{Failure, Success, Try}
class TRAPConverter extends ObjectCreateConverter[TrapDeployable]() {
override def ConstructorData(obj : TrapDeployable) : Try[TRAPData] = {
val health = 255 * obj.Health / obj.MaxHealth //TODO not precise
val health = StatConverter.Health(obj.Health, obj.MaxHealth)
if(health > 0) {
Success(
TRAPData(
SmallDeployableData(
CommonFieldDataWithPlacement(
PlacementData(obj.Position, obj.Orientation),
obj.Faction,
bops = false,
destroyed = false,
unk1 = 0,
jammered = false,
unk2 = false,
obj.Owner match {
case Some(owner) => owner
case None => PlanetSideGUID(0)
}
CommonFieldData(
obj.Faction,
bops = false,
alternate = false,
true,
None,
false,
Some(true),
None,
obj.Owner match {
case Some(owner) => owner
case None => PlanetSideGUID(0)
}
)
),
health
)
@ -33,15 +37,19 @@ class TRAPConverter extends ObjectCreateConverter[TrapDeployable]() {
else {
Success(
TRAPData(
SmallDeployableData(
CommonFieldDataWithPlacement(
PlacementData(obj.Position, obj.Orientation),
obj.Faction,
bops = false,
destroyed = true,
unk1 = 0,
jammered = false,
unk2 = false,
owner_guid = PlanetSideGUID(0)
CommonFieldData(
obj.Faction,
bops = false,
alternate = true,
true,
None,
false,
Some(true),
None,
PlanetSideGUID(0)
)
),
0
)

View file

@ -2,24 +2,53 @@
package net.psforever.objects.definition.converter
import net.psforever.objects.Telepad
import net.psforever.packet.game.objectcreate._
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate.{CommonFieldData, DetailedConstructionToolData, HandheldData}
import scala.util.{Failure, Success, Try}
class TelepadConverter extends ObjectCreateConverter[Telepad]() {
override def ConstructorData(obj : Telepad) : Try[TelepadData] = {
override def ConstructorData(obj : Telepad) : Try[HandheldData] = {
obj.Router match {
case Some(_) =>
Success(TelepadData (0, obj.Router))
case Some(router) =>
Success(
HandheldData(
CommonFieldData(
obj.Faction,
false,
false,
false,
None,
false,
None,
Some(router.guid),
PlanetSideGUID(0)
)
)
)
case None =>
Failure(new IllegalStateException("TelepadConverter: telepad needs to know id of its router"))
}
}
override def DetailedConstructorData(obj : Telepad) : Try[DetailedTelepadData] = {
override def DetailedConstructorData(obj : Telepad) : Try[DetailedConstructionToolData] = {
obj.Router match {
case Some(_) =>
Success(DetailedTelepadData (0, obj.Router))
case Some(router) =>
Success(
DetailedConstructionToolData(
CommonFieldData(
obj.Faction,
false,
false,
true,
None,
false,
None,
Some(router.guid),
PlanetSideGUID(0)
)
)
)
case None =>
Failure(new IllegalStateException("TelepadConverter: telepad needs to know id of its router"))
}

View file

@ -8,39 +8,59 @@ import net.psforever.packet.game.objectcreate._
import scala.util.{Failure, Success, Try}
class TelepadDeployableConverter extends ObjectCreateConverter[TelepadDeployable]() {
override def ConstructorData(obj : TelepadDeployable) : Try[TelepadDeployableData] = {
if(obj.Router.isEmpty || obj.Router.contains(PlanetSideGUID(0))) {
Failure(new IllegalStateException("TelepadDeployableConverter: telepad deployable needs to know id of its router"))
}
else {
if(obj.Health > 0) {
Success(TelepadDeployableData(
PlacementData(obj.Position, obj.Orientation),
obj.Faction,
bops = false,
destroyed = false,
unk1 = 2,
unk2 = true,
obj.Router.get,
obj.Owner.getOrElse(PlanetSideGUID(0)),
unk3 = 87,
unk4 = 12
))
}
else {
Success(TelepadDeployableData(
PlacementData(obj.Position, obj.Orientation),
obj.Faction,
bops = false,
destroyed = true,
unk1 = 2,
unk2 = true,
obj.Router.get,
owner_guid = PlanetSideGUID(0),
unk3 = 0,
unk4 = 6
))
}
override def ConstructorData(obj : TelepadDeployable) : Try[DroppedItemData[TelepadDeployableData]] = {
obj.Router match {
case Some(PlanetSideGUID(0)) =>
Failure(new IllegalStateException("TelepadDeployableConverter: knowledge of associated Router is null"))
case Some(router) =>
if(obj.Health > 0) {
Success(
DroppedItemData(
PlacementData(obj.Position, obj.Orientation),
TelepadDeployableData(
CommonFieldData(
obj.Faction,
bops = false,
alternate = false,
true,
None,
false,
None,
Some(router.guid),
obj.Owner.getOrElse(PlanetSideGUID(0))
),
unk1 = 87,
unk2 = 12
)
)
)
}
else {
Success(
DroppedItemData(
PlacementData(obj.Position, obj.Orientation),
TelepadDeployableData(
CommonFieldData(
obj.Faction,
bops = false,
alternate = true,
true,
None,
false,
None,
Some(router.guid),
PlanetSideGUID(0)
),
unk1 = 0,
unk2 = 6
)
)
)
}
case None =>
Failure(new IllegalStateException("TelepadDeployableConverter: telepad needs to know id of its associated Router"))
}
}
}

View file

@ -2,10 +2,10 @@
package net.psforever.objects.definition.converter
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.packet.game.objectcreate.CommonTerminalData
import net.psforever.packet.game.objectcreate.CommonFieldData
import scala.util.{Success, Try}
class TerminalConverter extends ObjectCreateConverter[Terminal]() {
override def ConstructorData(obj : Terminal) : Try[CommonTerminalData] = { Success(CommonTerminalData(obj.Faction)) }
override def ConstructorData(obj : Terminal) : Try[CommonFieldData] = { Success(CommonFieldData(obj.Faction)(false)) }
}

View file

@ -2,29 +2,57 @@
package net.psforever.objects.definition.converter
import net.psforever.objects.Tool
import net.psforever.packet.game.objectcreate.{DetailedWeaponData, InternalSlot, WeaponData}
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate.{CommonFieldData, DetailedWeaponData, InternalSlot, WeaponData}
import scala.collection.mutable.ListBuffer
import scala.util.{Success, Try}
class ToolConverter extends ObjectCreateConverter[Tool]() {
override def ConstructorData(obj : Tool) : Try[WeaponData] = {
val maxSlot : Int = obj.MaxAmmoSlot
val slots : ListBuffer[InternalSlot] = ListBuffer[InternalSlot]()
(0 until maxSlot).foreach(index => {
val slots : List[InternalSlot] = (0 until obj.MaxAmmoSlot).map(index => {
val box = obj.AmmoSlots(index).Box
slots += InternalSlot(box.Definition.ObjectId, box.GUID, index, box.Definition.Packet.ConstructorData(box).get)
})
Success(WeaponData(4,8, obj.FireModeIndex, slots.toList)(maxSlot))
InternalSlot(box.Definition.ObjectId, box.GUID, index, box.Definition.Packet.ConstructorData(box).get)
}).toList
Success(
WeaponData(
CommonFieldData(
obj.Faction,
bops = false,
alternate = false,
true,
None,
false,
None,
None,
PlanetSideGUID(0)
),
obj.FireModeIndex,
slots
)
)
}
override def DetailedConstructorData(obj : Tool) : Try[DetailedWeaponData] = {
val maxSlot : Int = obj.MaxAmmoSlot
val slots : ListBuffer[InternalSlot] = ListBuffer[InternalSlot]()
(0 until maxSlot).foreach(index => {
val slots : List[InternalSlot] = (0 until obj.MaxAmmoSlot).map(index => {
val box = obj.AmmoSlots(index).Box
slots += InternalSlot(box.Definition.ObjectId, box.GUID, index, box.Definition.Packet.DetailedConstructorData(box).get)
})
Success(DetailedWeaponData(4,8, obj.FireModeIndex, slots.toList)(maxSlot))
InternalSlot(box.Definition.ObjectId, box.GUID, index, box.Definition.Packet.DetailedConstructorData(box).get)
}).toList
Success(
DetailedWeaponData(
CommonFieldData(
obj.Faction,
bops = false,
alternate = false,
true,
None,
false,
None,
None,
PlanetSideGUID(0)
),
obj.FireModeIndex,
slots
)
)
}
}

View file

@ -4,7 +4,7 @@ package net.psforever.objects.definition.converter
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.Vehicle
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate.{InventoryData, InventoryItemData, ObjectClass, PlacementData, SpecificVehicleData, VehicleData, VehicleFormat}
import net.psforever.packet.game.objectcreate._
import net.psforever.types.DriveState
import scala.util.{Failure, Success, Try}
@ -14,21 +14,25 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
Failure(new Exception("VehicleConverter should not be used to generate detailed VehicleData (nothing should)"))
override def ConstructorData(obj : Vehicle) : Try[VehicleData] = {
val health = 255 * obj.Health / obj.MaxHealth //TODO not precise
val health = StatConverter.Health(obj.Health, obj.MaxHealth)
if(health > 0) { //active
Success(
VehicleData(
PlacementData(obj.Position, obj.Orientation, obj.Velocity),
obj.Faction,
bops = false,
destroyed = false,
unk1 = 0,
obj.Jammered,
unk2 = false,
obj.Owner match {
case Some(owner) => owner
case None => PlanetSideGUID(0)
},
CommonFieldData(
obj.Faction,
bops = false,
alternate = false,
v1 = false,
v2 = None,
v3 = false,
v4 = Some(false),
v5 = None,
obj.Owner match {
case Some(owner) => owner
case None => PlanetSideGUID(0)
}
),
unk3 = false,
health,
unk4 = false,
@ -46,13 +50,17 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
Success(
VehicleData(
PlacementData(obj.Position, obj.Orientation),
obj.Faction,
bops = false,
destroyed = true,
unk1 = 0,
jammered = false,
unk2 = false,
owner_guid = PlanetSideGUID(0),
CommonFieldData(
obj.Faction,
bops = false,
alternate = true,
v1 = false,
v2 = None,
v3 = false,
v4 = Some(false),
v5 = None,
guid = PlanetSideGUID(0)
),
unk3 = false,
health = 0,
unk4 = false,
@ -80,7 +88,7 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
private def MakeMountings(obj : Vehicle) : List[InventoryItemData.InventoryItem] = {
obj.Weapons.map({
case((index, slot)) =>
case(index, slot) =>
val equip : Equipment = slot.Equipment.get
val equipDef = equip.Definition
InventoryItemData(equipDef.ObjectId, equip.GUID, index, equipDef.Packet.ConstructorData(equip).get)

View file

@ -1,6 +1,9 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.equipment
/**
* An `Enumeration` of all the construction tool objects in the game, paired with their object id as the `Value`.
*/
object CItem extends Enumeration {
final val ace = Value(32)
final val advanced_ace = Value(39) //fdu

View file

@ -4,6 +4,8 @@ package net.psforever.objects.equipment
import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.definition.EquipmentDefinition
import net.psforever.objects.inventory.InventoryTile
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.types.PlanetSideEmpire
/**
* `Equipment` is anything that can be:
@ -12,7 +14,17 @@ import net.psforever.objects.inventory.InventoryTile
* and, special carried (like a lattice logic unit);
* and, dropped on the ground in the game world and render where it was deposited.
*/
abstract class Equipment extends PlanetSideGameObject {
abstract class Equipment extends PlanetSideGameObject
with FactionAffinity {
private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
def Faction : PlanetSideEmpire.Value = faction
override def Faction_=(fact : PlanetSideEmpire.Value) : PlanetSideEmpire.Value = {
faction = fact
Faction
}
def Size : EquipmentSize.Value = Definition.Size
def Tile : InventoryTile = Definition.Tile

View file

@ -1,6 +1,10 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.equipment
/**
* An `Enumeration` of common equipment sizes in the game.
* Check the comments for originating use.
*/
object EquipmentSize extends Enumeration {
val
Blocked,

View file

@ -1,7 +1,5 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
import net.psforever.objects.equipment.{Equipment, EquipmentSize}
package net.psforever.objects.equipment
/**
* A size-checked unit of storage (or mounting) for `Equipment`.

View file

@ -1,6 +1,9 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.equipment
/**
* An `Enumeration` of some activation-type `Equipment` in the game, paired with their object id as the `Value`.
*/
object SItem extends Enumeration {
final val boomer_trigger = Value(149)
final val command_detonater = Value(213) //cud

View file

@ -3,7 +3,7 @@ package net.psforever.objects.guid
import akka.actor.ActorRef
import net.psforever.objects.entity.IdentifiableEntity
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
import net.psforever.objects._
import net.psforever.objects.inventory.Container
import net.psforever.objects.serverobject.turret.WeaponTurret

View file

@ -1,8 +1,8 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.inventory
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.{EquipmentSlot, OffhandEquipmentSlot}
import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
import net.psforever.objects.OffhandEquipmentSlot
import net.psforever.packet.game.PlanetSideGUID
import scala.util.Try

View file

@ -3,8 +3,7 @@ package net.psforever.objects.inventory
import java.util.concurrent.atomic.AtomicInteger
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.EquipmentSlot
import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
import net.psforever.packet.game.PlanetSideGUID
import scala.annotation.tailrec

View file

@ -3,7 +3,7 @@ package net.psforever.objects.loadouts
import net.psforever.objects._
import net.psforever.objects.definition._
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
import net.psforever.objects.inventory.InventoryItem
import scala.annotation.tailrec

View file

@ -1,7 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
class AirVehicleTerminalDefinition extends VehicleTerminalDefinition(43) {
vehicles = flight1Vehicles
Name = "air_vehicle_terminal"
}

View file

@ -1,7 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
class BFRTerminalDefinition extends VehicleTerminalDefinition(143) {
vehicles = bfrVehicles
Name = "bfr_terminal"
}

View file

@ -1,22 +1,14 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
import net.psforever.objects.Player
import net.psforever.packet.game.ItemTransactionMessage
import net.psforever.types.CertificationType
/**
* The definition for any `Terminal` that is of a type "cert_terminal" (certification terminal).
* `Learn` and `Sell` `CertificationType` entries, gaining access to different `Equipment` and `Vehicles`.
*/
class CertTerminalDefinition extends TerminalDefinition(171) {
Name = "cert_terminal"
object CertTerminalDefinition {
/**
* The certifications available.
* All entries are listed on page (tab) number 0.
*/
private val certificationList : Map[String, CertificationType.Value] = Map(
val certs : Map[String, CertificationType.Value] = Map(
"medium_assault" -> CertificationType.MediumAssault,
"reinforced_armor" -> CertificationType.ReinforcedExoSuit,
"quad_all" -> CertificationType.ATV,
@ -59,34 +51,4 @@ class CertTerminalDefinition extends TerminalDefinition(171) {
"advanced_medical" -> CertificationType.AdvancedMedical
//TODO bfr certification entries
)
/**
* Process a `TransactionType.Learn` action by the user.
* @param player the player
* @param msg the original packet carrying the request
* @return an actionable message that explains how to process the request
*/
def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = { //Learn
certificationList.get(msg.item_name) match {
case Some(cert) =>
Terminal.LearnCertification(cert)
case None =>
Terminal.NoDeal()
}
}
/**
* Process a `TransactionType.Sell` action by the user.
* @param player the player
* @param msg the original packet carrying the request
* @return an actionable message that explains how to process the request
*/
override def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
certificationList.get(msg.item_name) match {
case Some(cert) =>
Terminal.SellCertification(cert)
case None =>
Terminal.NoDeal()
}
}
}

View file

@ -1,7 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
class DropshipVehicleTerminalDefinition extends VehicleTerminalDefinition(263) {
vehicles = flight1Vehicles ++ flight2Vehicles
Name = "dropship_vehicle_terminal"
}

View file

@ -10,24 +10,6 @@ import net.psforever.types.ExoSuitType
import scala.annotation.switch
abstract class EquipmentTerminalDefinition(objId : Int) extends TerminalDefinition(objId) {
Name = "equipment_terminal"
/**
* Process a `TransactionType.Sell` action by the user.
* There is no specific tab associated with this action.
* It is a common button on the terminal interface window.
* Additionally, the equipment to be sold ia almost always in the player's `FreeHand` slot.
* Selling `Equipment` is always permitted.
* @param player the player
* @param msg the original packet carrying the request
* @return an actionable message that explains how to process the request
*/
override def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
Terminal.SellEquipment()
}
}
object EquipmentTerminalDefinition {
private[this] val log = org.log4s.getLogger("TerminalDefinition")

View file

@ -1,7 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
class GroundVehicleTerminalDefinition extends VehicleTerminalDefinition(386) {
vehicles = groundVehicles
Name = "ground_vehicle_terminal"
}

View file

@ -0,0 +1,27 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
import net.psforever.objects.GlobalDefinitions
import net.psforever.objects.definition.ImplantDefinition
/**
* Data for the `Definition` for any `Terminal` that is of a type "implant_terminal_interface."
* Implant terminals are composed of two components.
* This `Definition` constructs the invisible interface component (interacted with as a game window).
* Unlike other `Terminal` objects in the game, this one must be constructed on the client and
* attached as a child of the visible implant terminal component - the "implant_terminal_mech."
*/
object ImplantTerminalDefinition {
val implants : Map[String, ImplantDefinition] = Map (
"advanced_regen" -> GlobalDefinitions.advanced_regen,
"targeting" -> GlobalDefinitions.targeting,
"audio_amplifier" -> GlobalDefinitions.audio_amplifier,
"darklight_vision" -> GlobalDefinitions.darklight_vision,
"melee_booster" -> GlobalDefinitions.melee_booster,
"personal_shield" -> GlobalDefinitions.personal_shield,
"range_magnifier" -> GlobalDefinitions.range_magnifier,
"second_wind" -> GlobalDefinitions.second_wind,
"silent_run" -> GlobalDefinitions.silent_run,
"surge" -> GlobalDefinitions.surge
)
}

View file

@ -1,51 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
import net.psforever.objects.{GlobalDefinitions, Player}
import net.psforever.objects.definition.ImplantDefinition
import net.psforever.objects.definition.converter.ImplantTerminalInterfaceConverter
import net.psforever.packet.game.ItemTransactionMessage
import net.psforever.packet.game.objectcreate.ObjectClass
/**
* The `Definition` for any `Terminal` that is of a type "implant_terminal_interface."
* Implant terminals are composed of two components.
* This `Definition` constructs the invisible interface component (interacted with as a game window).
* Unlike other `Terminal` objects in the game, this one must be constructed on the client and
* attached as a child of the visible implant terminal component - the "implant_terminal_mech."
*/
class ImplantTerminalInterfaceDefinition extends TerminalDefinition(ObjectClass.implant_terminal_interface) {
Packet = new ImplantTerminalInterfaceConverter
Name = "implant_terminal_interface"
private val implants : Map[String, ImplantDefinition] = Map (
"advanced_regen" -> GlobalDefinitions.advanced_regen,
"targeting" -> GlobalDefinitions.targeting,
"audio_amplifier" -> GlobalDefinitions.audio_amplifier,
"darklight_vision" -> GlobalDefinitions.darklight_vision,
"melee_booster" -> GlobalDefinitions.melee_booster,
"personal_shield" -> GlobalDefinitions.personal_shield,
"range_magnifier" -> GlobalDefinitions.range_magnifier,
"second_wind" -> GlobalDefinitions.second_wind,
"silent_run" -> GlobalDefinitions.silent_run,
"surge" -> GlobalDefinitions.surge
)
def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
implants.get(msg.item_name) match {
case Some(implant) =>
Terminal.LearnImplant(implant)
case None =>
Terminal.NoDeal()
}
}
override def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
implants.get(msg.item_name) match {
case Some(implant) =>
Terminal.SellImplant(implant)
case None =>
Terminal.NoDeal()
}
}
}

View file

@ -4,29 +4,19 @@ package net.psforever.objects.serverobject.terminals
import akka.actor.ActorContext
import net.psforever.objects.Player
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.packet.game.ItemTransactionMessage
/**
* The definition for any `Terminal` that is of a type "matrix_terminal".
* Matrix terminal objects are used to create anchor points in the game environment
* in reference to a working set of spawn points attached to a `Building` object or `Vehicle` object
* depending on the spawn group.
* @see `SpawnTube`
* @see `Zone.CreateSpawnGroups`
* @see `Zone.SpawnGroups`
* @param objectId the object's identifier number
*/
class MatrixTerminalDefinition(object_id : Int) extends TerminalDefinition(object_id) {
Name = if(object_id == 517) {
"matrix_terminala"
}
else if(object_id == 518) {
"matrix_terminalb"
}
else if(object_id == 519) {
"matrix_terminalc"
}
else if(object_id == 812) {
"spawn_terminal"
}
else {
throw new IllegalArgumentException("terminal must be object id 517-519 or 812")
}
override def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal()
class MatrixTerminalDefinition(objectId : Int) extends TerminalDefinition(objectId) {
def Request(player : Player, msg : Any) : Terminal.Exchange = Terminal.NoDeal()
}
object MatrixTerminalDefinition {

View file

@ -1,6 +1,8 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
import net.psforever.objects.Player
import scala.concurrent.duration.{Duration, FiniteDuration}
/**
@ -8,7 +10,7 @@ import scala.concurrent.duration.{Duration, FiniteDuration}
* This includes the functionality of the formal medical terminals and some of the cavern crystals.
* Do not confuse the game's internal "medical_terminal" object category and the actual `medical_terminal` object (529).
*/
class MedicalTerminalDefinition(objectId : Int) extends TerminalDefinition(objectId) with ProximityDefinition {
class MedicalTerminalDefinition(objectId : Int) extends ProximityTerminalDefinition(objectId) {
private var interval : FiniteDuration = Duration(0, "seconds")
private var healAmount : Int = 0
private var armorAmount : Int = 0
@ -37,4 +39,6 @@ class MedicalTerminalDefinition(objectId : Int) extends TerminalDefinition(objec
armorAmount = amount
ArmorAmount
}
override def Request(player : Player, msg : Any) : Terminal.Exchange = Terminal.NoDeal()
}

View file

@ -1,88 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
import akka.actor.ActorContext
import net.psforever.objects.Player
import net.psforever.objects.loadouts.InfantryLoadout
import net.psforever.objects.inventory.InventoryItem
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.packet.game.ItemTransactionMessage
import net.psforever.objects.serverobject.terminals.EquipmentTerminalDefinition._
import net.psforever.types.ExoSuitType
/**
* The definition for any `Terminal` that is of a type "ams_order_terminal".
* As the name indicates, paired on the flanks of an advanced mobile spawn vehicle.<br>
* <br>
* `Buy` and `Sell` `Equipment` items and `AmmoBox` items.
* Change `ExoSuitType` and retrieve `Loadout` entries.
* Changing into mechanized assault exo-suits (MAXes) is not permitted.
*/
class OrderTerminalABDefinition(object_id : Int) extends EquipmentTerminalDefinition(object_id) {
if(object_id == 613) {
Name = "order_terminala"
}
else if(object_id == 614) {
Name = "order_terminalb"
}
else {
throw new IllegalArgumentException("terminal must be either object id 613 or object id 614")
}
/**
* The `Equipment` available from this `Terminal` on specific pages.
*/
private val buyFunc : (Player, ItemTransactionMessage)=>Terminal.Exchange =
EquipmentTerminalDefinition.Buy(
infantryAmmunition ++ infantryWeapons,
supportAmmunition ++ supportWeapons,
suits
)
override def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = buyFunc(player, msg)
/**
* Process a `TransactionType.Loadout` action by the user.
* `Loadout` objects are blueprints composed of exo-suit specifications and simplified `Equipment`-to-slot mappings.
* If a valid loadout is found, its data is transformed back into actual `Equipment` for return to the user.
* Loadouts that would suit the player into a mechanized assault exo-suit are not permitted.
* @param player the player
* @param msg the original packet carrying the request
* @return an actionable message that explains how to process the request
*/
override def Loadout(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
if(msg.item_page == 4) { //Favorites tab
player.LoadLoadout(msg.unk1) match {
case Some(loadout : InfantryLoadout) =>
if(loadout.exosuit != ExoSuitType.MAX) {
val holsters = loadout.visible_slots.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
val inventory = loadout.inventory.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
Terminal.InfantryLoadout(loadout.exosuit, loadout.subtype, holsters, inventory)
}
else {
Terminal.NoDeal()
}
case Some(_) | None =>
Terminal.NoDeal()
}
}
else {
Terminal.NoDeal()
}
}
}
object OrderTerminalABDefinition {
/**
* Assemble some logic for a provided object.
* @param obj an `Amenity` object;
* anticipating a `Terminal` object using this same definition
* @param context hook to the local `Actor` system
*/
def Setup(obj : Amenity, context : ActorContext) : Unit = {
import akka.actor.{ActorRef, Props}
if(obj.Actor == ActorRef.noSender) {
obj.Actor = context.actorOf(Props(classOf[TerminalControl], obj), s"${obj.Definition.Name}_${obj.GUID.guid}")
}
}
}

View file

@ -10,61 +10,39 @@ import net.psforever.objects.inventory.InventoryItem
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.packet.game.ItemTransactionMessage
import net.psforever.objects.serverobject.terminals.EquipmentTerminalDefinition._
import net.psforever.types.{CertificationType, ExoSuitType}
import net.psforever.types.{CertificationType, ExoSuitType, TransactionType}
import scala.collection.mutable
/**
* The definition for any `Terminal` that is of a type "order_terminal".
* This kind of "order_terminal" is applicable to facilities.<br>
* The definition for any `Terminal` from which specifications can be altered.
* These specification alternations involve three classifications:
* the exchange of denominations of in-game hardware, i.e., `Equipment`,
* the modification of lists of personal statistics, e.g., `Certifications`,
* and saving and loading of preset configurations, i.e., `Loadouts`.
* This hardware is organized as "stock," occasionally supplemented.
* Terminals have tabs (visually) that are organized by different stock (internally)
* that determines the behavior available from that tab
* and what stock can be drawn or returned.<br>
* <br>
* `Buy` and `Sell` `Equipment` items and `AmmoBox` items.
* Change `ExoSuitType` and retrieve `Loadout` entries.
* Equipment terminals are the property of bases and vehicles ("amenities").
* To bases, the `Terminal` object is coupled loosely and may be allowed to diverge.
* To vehicles, the `Terminal` object is coupled directly to the faction affiliation of the vehicle.
* @see `Amenity`
* @see `Terminal`
* @see `Utility`
*/
class OrderTerminalDefinition extends EquipmentTerminalDefinition(612) {
Name = "order_terminal"
class OrderTerminalDefinition(objId : Int) extends TerminalDefinition(objId) {
/** An internal object organizing the different specification options found on a terminal's UI. */
private val tabs : mutable.HashMap[Int, OrderTerminalDefinition.Tab] =
new mutable.HashMap[Int, OrderTerminalDefinition.Tab]()
/** Disconnect the ability to return stock back to the terminal
* from the type of stock available from the terminal in general
* or the type of stock available from its denoted page.
* Will always return a message of type `SellEquipment`.*/
private var sellEquipmentDefault : Boolean = false
/**
* The `Equipment` available from this `Terminal` on specific pages.
*/
private val buyFunc : (Player, ItemTransactionMessage)=>Terminal.Exchange = EquipmentTerminalDefinition.Buy(
infantryAmmunition ++ infantryWeapons,
supportAmmunition ++ supportWeapons,
suits ++ maxSuits)
override def Buy(player: Player, msg : ItemTransactionMessage) : Terminal.Exchange = buyFunc(player, msg)
/**
* Process a `TransactionType.Loadout` action by the user.
* `Loadout` objects are blueprints composed of exo-suit specifications and simplified `Equipment`-to-slot mappings.
* If a valid loadout is found, its data is transformed back into actual `Equipment` for return to the user.
* @param player the player
* @param msg the original packet carrying the request
* @return an actionable message that explains how to process the request
*/
override def Loadout(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
if(msg.item_page == 4) { //Favorites tab
player.LoadLoadout(msg.unk1) match {
case Some(loadout : InfantryLoadout) =>
val holsters = loadout.visible_slots.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
val inventory = loadout.inventory.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
Terminal.InfantryLoadout(loadout.exosuit, loadout.subtype, holsters, inventory)
case Some(_) | None =>
Terminal.NoDeal()
}
}
else {
Terminal.NoDeal()
}
}
}
class _OrderTerminalDefinition(objId : Int) extends TerminalDefinition(objId) {
private val pages : mutable.HashMap[Int, _OrderTerminalDefinition.PageDefinition] =
new mutable.HashMap[Int, _OrderTerminalDefinition.PageDefinition]()
private var sellEquipmentDefault : Boolean = true
def Page : mutable.HashMap[Int, _OrderTerminalDefinition.PageDefinition] = pages
def Tab : mutable.HashMap[Int, OrderTerminalDefinition.Tab] = tabs
def SellEquipmentByDefault : Boolean = sellEquipmentDefault
@ -73,8 +51,24 @@ class _OrderTerminalDefinition(objId : Int) extends TerminalDefinition(objId) {
SellEquipmentByDefault
}
override def Buy(player: Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
pages.get(msg.item_page) match {
def Request(player : Player, msg : Any) : Terminal.Exchange = msg match {
case message : ItemTransactionMessage =>
message.transaction_type match {
case TransactionType.Buy | TransactionType.Learn =>
Buy(player, message)
case TransactionType.Loadout =>
Loadout(player, message)
case TransactionType.Sell =>
Sell(player, message)
case _ =>
Terminal.NoDeal()
}
case _ =>
Terminal.NoDeal()
}
private def Buy(player: Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
tabs.get(msg.item_page) match {
case Some(page) =>
page.Buy(player, msg)
case _ =>
@ -82,14 +76,14 @@ class _OrderTerminalDefinition(objId : Int) extends TerminalDefinition(objId) {
}
}
override def Loadout(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Buy(player, msg)
private def Loadout(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Buy(player, msg)
override def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
private def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
if(sellEquipmentDefault) {
Terminal.SellEquipment()
}
else {
pages.get(msg.item_page) match {
tabs.get(msg.item_page) match {
case Some(page) =>
page.Sell(player, msg)
case _ =>
@ -99,14 +93,24 @@ class _OrderTerminalDefinition(objId : Int) extends TerminalDefinition(objId) {
}
}
object _OrderTerminalDefinition {
abstract class PageDefinition(stock : Map[String, Any]) {
def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange
def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange
object OrderTerminalDefinition {
/**
* A basic tab outlining the specific type of stock available from this part of the terminal's interface.
* @see `ItemTransactionMessage`
*/
sealed trait Tab {
def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal()
def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal()
}
final case class ArmorPage(stock : Map[String, (ExoSuitType.Value, Int)]) extends PageDefinition(stock) {
def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
/**
* The tab used to select an exo-suit to be worn by the player.
* @see `ExoSuitType`
* @param stock the key is always a `String` value as defined from `ItemTransationMessage` data;
* the value is a tuple composed of an `ExoSuitType` value and a subtype value
*/
final case class ArmorPage(stock : Map[String, (ExoSuitType.Value, Int)]) extends Tab {
override def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
stock.get(msg.item_name) match {
case Some((suit : ExoSuitType.Value, subtype : Int)) =>
Terminal.BuyExosuit(suit, subtype)
@ -114,12 +118,42 @@ object _OrderTerminalDefinition {
Terminal.NoDeal()
}
}
def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal()
}
final case class CertificationPage(stock : Map[String, CertificationType.Value]) extends PageDefinition(stock) {
def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
/**
* An expanded form of the tab used to select an exo-suit to be worn by the player that also provides some equipment.
* @see `ExoSuitType`
* @see `Equipment`
* @param stock the key is always a `String` value as defined from `ItemTransationMessage` data;
* the value is a tuple composed of an `ExoSuitType` value and a subtype value
* @param items the key is always a `String` value as defined from `ItemTransationMessage` data;
* the value is a curried function that produces an `Equipment` object
*/
final case class ArmorWithAmmoPage(stock : Map[String, (ExoSuitType.Value, Int)], items : Map[String, ()=>Equipment]) extends Tab {
override def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
stock.get(msg.item_name) match {
case Some((suit : ExoSuitType.Value, subtype : Int)) =>
Terminal.BuyExosuit(suit, subtype)
case _ =>
items.get(msg.item_name) match {
case Some(item : (()=>Equipment)) =>
Terminal.BuyEquipment(item())
case _ =>
Terminal.NoDeal()
}
}
}
}
/**
* The tab used to select a certification to be utilized by the player.
* Only certifications may be returned to the interface defined by this page.
* @see `CertificationType`
* @param stock the key is always a `String` value as defined from `ItemTransationMessage` data;
* the value is a `CertificationType` value
*/
final case class CertificationPage(stock : Map[String, CertificationType.Value]) extends Tab {
override def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
stock.get(msg.item_name) match {
case Some(cert : CertificationType.Value) =>
Terminal.LearnCertification(cert)
@ -128,7 +162,7 @@ object _OrderTerminalDefinition {
}
}
def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
override def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
stock.get(msg.item_name) match {
case Some(cert : CertificationType.Value) =>
Terminal.SellCertification(cert)
@ -138,8 +172,13 @@ object _OrderTerminalDefinition {
}
}
final case class EquipmentPage(stock : Map[String, ()=>Equipment]) extends PageDefinition(stock) {
def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
/**
* The tab used to produce an `Equipment` object to be used by the player.
* @param stock the key is always a `String` value as defined from `ItemTransationMessage` data;
* the value is a curried function that produces an `Equipment` object
*/
final case class EquipmentPage(stock : Map[String, ()=>Equipment]) extends Tab {
override def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
stock.get(msg.item_name) match {
case Some(item : (()=>Equipment)) =>
Terminal.BuyEquipment(item())
@ -147,12 +186,18 @@ object _OrderTerminalDefinition {
Terminal.NoDeal()
}
}
def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.SellEquipment()
}
final case class ImplantPage(stock : Map[String, ImplantDefinition]) extends PageDefinition(stock) {
def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
/**
* The tab used to select an implant to be utilized by the player.
* A maximum of three implants can be obtained by any player at a time depending on the player's battle rank.
* Only implants may be returned to the interface defined by this page.
* @see `ImplantDefinition`
* @param stock the key is always a `String` value as defined from `ItemTransationMessage` data;
* the value is a `CertificationType` value
*/
final case class ImplantPage(stock : Map[String, ImplantDefinition]) extends Tab {
override def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
stock.get(msg.item_name) match {
case Some(implant : ImplantDefinition) =>
Terminal.LearnImplant(implant)
@ -161,7 +206,7 @@ object _OrderTerminalDefinition {
}
}
def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
override def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
stock.get(msg.item_name) match {
case Some(implant : ImplantDefinition) =>
Terminal.SellImplant(implant)
@ -171,8 +216,19 @@ object _OrderTerminalDefinition {
}
}
final case class InfantryLoadoutPage() extends PageDefinition(Map.empty) {
def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
/**
* The tab used to select which custom loadout the player is using.
* Player loadouts are defined by an exo-suit to be worn by the player
* and equipment in the holsters and the inventory.
* In this case, the reference to the player that is a parameter of the functions maintains information about the loadouts;
* no extra information specific to this page is necessary.
* @see `ExoSuitType`
* @see `Equipment`
* @see `InfantryLoadout`
* @see `Loadout`
*/
final case class InfantryLoadoutPage() extends Tab {
override def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
player.LoadLoadout(msg.unk1) match {
case Some(loadout : InfantryLoadout) =>
val holsters = loadout.visible_slots.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
@ -182,12 +238,20 @@ object _OrderTerminalDefinition {
Terminal.NoDeal()
}
}
def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal()
}
final case class VehicleLoadoutPage() extends PageDefinition(Map.empty) {
def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
/**
* The tab used to select which custom loadout the player's vehicle is using.
* Vehicle loadouts are defined by a (superfluous) redefinition of the vehicle's mounted weapons
* and equipment in the trunk.
* In this case, the reference to the player that is a parameter of the functions maintains information about the loadouts;
* no extra information specific to this page is necessary.
* @see `Equipment`
* @see `Loadout`
* @see `VehicleLoadout`
*/
final case class VehicleLoadoutPage() extends Tab {
override def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
player.LoadLoadout(msg.unk1 + 10) match {
case Some(loadout : VehicleLoadout) =>
val weapons = loadout.visible_slots.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) })
@ -197,13 +261,21 @@ object _OrderTerminalDefinition {
Terminal.NoDeal()
}
}
def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal()
}
/**
* The tab used to select a vehicle to be spawned for the player.
* Vehicle loadouts are defined by a superfluous redefinition of the vehicle's mounted weapons
* and equipment in the trunk
* for the purpose of establishing default contents.
* @see `Equipment`
* @see `Loadout`
* @see `Vehicle`
* @see `VehicleLoadout`
*/
import net.psforever.objects.loadouts.{Loadout => Contents} //distinguish from Terminal.Loadout message
final case class VehiclePage(stock : Map[String, ()=>Vehicle], trunk : Map[String, Contents]) extends PageDefinition(stock) {
def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
final case class VehiclePage(stock : Map[String, ()=>Vehicle], trunk : Map[String, Contents]) extends Tab {
override def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
stock.get(msg.item_name) match {
case Some(vehicle) =>
val (weapons, inventory) = trunk.get(msg.item_name) match {
@ -220,8 +292,6 @@ object _OrderTerminalDefinition {
Terminal.NoDeal()
}
}
def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal()
}
/**

View file

@ -1,20 +1,23 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
import net.psforever.objects.{PlanetSideGameObject, Player}
import net.psforever.packet.game.ItemTransactionMessage
import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.definition.ObjectDefinition
import scala.collection.mutable
/**
* The definition for any `Terminal` that possesses a proximity-based effect.
* The definition mix-in for any game object that possesses a proximity-based effect.
* This includes the limited proximity-based functionality of the formal medical terminals
* and the actual proximity-based functionality of the cavern crystals.
* Objects created by this definition being linked by their use of `ProximityTerminalUseMessage`.
* Objects created by this definition being linked by their communication
* between the server and client using `ProximityTerminalUseMessage` game packets.
*/
trait ProximityDefinition {
this : ObjectDefinition =>
private var useRadius : Float = 0f //TODO belongs on a wider range of object definitions
private val targetValidation : mutable.HashMap[ProximityTarget.Value, (PlanetSideGameObject)=>Boolean] = new mutable.HashMap[ProximityTarget.Value, (PlanetSideGameObject)=>Boolean]()
private val targetValidation : mutable.HashMap[ProximityTarget.Value, PlanetSideGameObject=>Boolean] = new mutable.HashMap[ProximityTarget.Value, PlanetSideGameObject=>Boolean]()
def UseRadius : Float = useRadius
@ -23,9 +26,9 @@ trait ProximityDefinition {
UseRadius
}
def TargetValidation : mutable.HashMap[ProximityTarget.Value, (PlanetSideGameObject)=>Boolean] = targetValidation
def TargetValidation : mutable.HashMap[ProximityTarget.Value, PlanetSideGameObject=>Boolean] = targetValidation
def Validations : Seq[(PlanetSideGameObject)=>Boolean] = {
def Validations : Seq[PlanetSideGameObject=>Boolean] = {
targetValidation.headOption match {
case Some(_) =>
targetValidation.values.toSeq
@ -33,10 +36,8 @@ trait ProximityDefinition {
Seq(ProximityDefinition.Invalid)
}
}
def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal()
}
object ProximityDefinition {
protected val Invalid : (PlanetSideGameObject=>Boolean) = (_ : PlanetSideGameObject) => false
protected val Invalid : PlanetSideGameObject=>Boolean = (_ : PlanetSideGameObject) => false
}

View file

@ -1,6 +1,8 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
import net.psforever.objects.Player
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.types.Vector3
import services.Service
@ -13,14 +15,23 @@ import services.Service
* For example, the cavern crystals are considered owner-neutral elements that are not attached to a `Building` object.
* @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
*/
class ProximityTerminal(tdef : TerminalDefinition with ProximityDefinition) extends Terminal(tdef) with ProximityUnit
class ProximityTerminal(tdef : ProximityTerminalDefinition) extends Terminal(tdef) with ProximityUnit {
override def Request(player : Player, msg : Any) : Terminal.Exchange = {
msg match {
case message : CommonMessages.Use =>
Actor ! message
case _ =>
}
Terminal.NoDeal()
}
}
object ProximityTerminal {
/**
* Overloaded constructor.
* @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
*/
def apply(tdef : TerminalDefinition with ProximityDefinition) : ProximityTerminal = {
def apply(tdef : ProximityTerminalDefinition) : ProximityTerminal = {
new ProximityTerminal(tdef)
}
@ -33,7 +44,7 @@ object ProximityTerminal {
* @param context a context to allow the object to properly set up `ActorSystem` functionality
* @return the `Terminal` object
*/
def Constructor(tdef : TerminalDefinition with ProximityDefinition)(id : Int, context : ActorContext) : Terminal = {
def Constructor(tdef : ProximityTerminalDefinition)(id : Int, context : ActorContext) : Terminal = {
import akka.actor.Props
val obj = ProximityTerminal(tdef)
obj.Actor = context.actorOf(Props(classOf[ProximityTerminalControl], obj), s"${tdef.Name}_$id")
@ -42,12 +53,13 @@ object ProximityTerminal {
/**
* Instantiate an configure a `Terminal` object, with position coordinates.
* @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
* @param id the unique id that will be assigned to this entity
* @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
* @param pos the location of the object
* @param id the unique id that will be assigned to this entity
* @param context a context to allow the object to properly set up `ActorSystem` functionality
* @return the `Terminal` object
*/
def Constructor(tdef : TerminalDefinition with ProximityDefinition, pos : Vector3)(id : Int, context : ActorContext) : Terminal = {
def Constructor(tdef : ProximityTerminalDefinition, pos : Vector3)(id : Int, context : ActorContext) : Terminal = {
import akka.actor.Props
val obj = ProximityTerminal(tdef)
obj.Position = pos

View file

@ -0,0 +1,13 @@
// Copyright (c) 2019 PSForever
package net.psforever.objects.serverobject.terminals
import net.psforever.objects.Player
/**
*The definition for any `Terminal` that can be accessed for amenities and services,
* triggered when a certain distance from the unit itself (proximity-based).
* @param objectId the object's identifier number
*/
class ProximityTerminalDefinition(objectId : Int) extends TerminalDefinition(objectId) with ProximityDefinition {
def Request(player : Player, msg : Any) : Terminal.Exchange = Terminal.NoDeal()
}

View file

@ -1,30 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
import akka.actor.ActorContext
import net.psforever.objects.Player
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.packet.game.ItemTransactionMessage
class TeleportPadTerminalDefinition extends EquipmentTerminalDefinition(853) {
Name = "teleport_pad_terminal"
def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
Terminal.BuyEquipment(EquipmentTerminalDefinition.routerTerminal("router_telepad")())
}
}
object TeleportPadTerminalDefinition {
/**
* Assemble some logic for a provided object.
* @param obj an `Amenity` object;
* anticipating a `Terminal` object using this same definition
* @param context hook to the local `Actor` system
*/
def Setup(obj : Amenity, context : ActorContext) : Unit = {
import akka.actor.{ActorRef, Props}
if(obj.Actor == ActorRef.noSender) {
obj.Actor = context.actorOf(Props(classOf[TerminalControl], obj), s"${obj.Definition.Name}_${obj.GUID.guid}")
}
}
}

View file

@ -6,10 +6,14 @@ import net.psforever.objects.definition.VehicleDefinition
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.packet.game.{ItemTransactionMessage, TriggeredSound}
import net.psforever.types.TransactionType
/**
* A structure-owned server object that is a "terminal" that can be accessed for amenities and services.
* A server object that can be accessed for services and other amenities.
* Terminals are owned by both `Structure` objects and by `Vehicle` objects
* and generally conform to the faction affiliation of the owner.
* Some `Structure`-owned terminals may be compromised
* to extend functionality to other's not of faction affiliation for a short time
* while `Vehicle`-owned terminals may not.
* @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
*/
class Terminal(tdef : TerminalDefinition) extends Amenity with Hackable {
@ -31,44 +35,23 @@ class Terminal(tdef : TerminalDefinition) extends Amenity with Hackable {
}
/**
* Process some `TransactionType` action requested by the user.
* Process a message (a "request") dispatched by the user.
* To be accessible, the terminal must be owned by the same faction by the user or must be compromised.
* @see `FactionAffinity`
* @see `PlanetSideEmpire`
* @param player the player
* @param msg the original packet carrying the request
* @return an actionable message that explains what resulted from interacting with this `Terminal`
*/
def Request(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
def Request(player : Player, msg : Any) : Terminal.Exchange = {
if(Faction == player.Faction || HackedBy.isDefined) {
msg.transaction_type match {
case TransactionType.Buy | TransactionType.Learn =>
Buy(player, msg)
case TransactionType.Sell =>
Sell(player, msg)
case TransactionType.Loadout =>
Loadout(player, msg)
case _ =>
Terminal.NoDeal()
}
tdef.Request(player, msg)
}
else {
Terminal.NoDeal()
}
}
def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
tdef.Buy(player, msg)
}
def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
tdef.Sell(player, msg)
}
def Loadout(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
tdef.Loadout(player, msg)
}
def Definition : TerminalDefinition = tdef
}

View file

@ -3,10 +3,9 @@ package net.psforever.objects.serverobject.terminals
import net.psforever.objects.Player
import net.psforever.objects.definition.converter.TerminalConverter
import net.psforever.packet.game.ItemTransactionMessage
/**
* The basic definition for any `Terminal`.
* The basic definition for any `Terminal` object.
* @param objectId the object's identifier number
*/
abstract class TerminalDefinition(objectId : Int) extends net.psforever.objects.definition.ObjectDefinition(objectId) {
@ -14,17 +13,12 @@ abstract class TerminalDefinition(objectId : Int) extends net.psforever.objects.
Packet = new TerminalConverter
/**
* The unimplemented functionality for this `Terminal`'s `TransactionType.Buy` and `TransactionType.Learn` activity.
* The unimplemented functionality for the entry function of form of activity
* processed by this terminal and codified by the input message (a "request").
* @see `Terminal.Exchange`
* @param player the player who made the request
* @param msg the request message
* @return a message that resolves the transaction
*/
def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange
/**
* The unimplemented functionality for this `Terminal`'s `TransactionType.Sell` activity.
*/
def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal()
/**
* The unimplemented functionality for this `Terminal`'s `TransactionType.Loadout` activity.
*/
def Loadout(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal()
def Request(player : Player, msg : Any) : Terminal.Exchange
}

View file

@ -1,7 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
class VehicleTerminalCombinedDefinition extends VehicleTerminalDefinition(952) {
vehicles = groundVehicles ++ flight1Vehicles
Name = "vehicle_terminal_combined"
}

View file

@ -2,23 +2,17 @@
package net.psforever.objects.serverobject.terminals
import net.psforever.objects.definition.VehicleDefinition
import net.psforever.objects.{Player, Vehicle}
import net.psforever.objects.Vehicle
import net.psforever.objects.loadouts.VehicleLoadout
import net.psforever.objects.inventory.InventoryItem
import net.psforever.packet.game.ItemTransactionMessage
abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition(objId) {
protected var vehicles : Map[String, ()=>Vehicle] = Map()
Name = "vehicle_terminal"
object VehicleTerminalDefinition {
import net.psforever.objects.GlobalDefinitions._
import VehicleTerminalDefinition.MakeVehicle
/**
* A `Map` of operations for producing a ground-based `Vehicle`.
* key - an identification string sent by the client
* value - a curried function that builds the object
*/
protected val groundVehicles : Map[String, () => Vehicle] = Map(
val groundVehicles : Map[String, () => Vehicle] = Map(
"quadassault" -> MakeVehicle(quadassault),
"fury" -> MakeVehicle(fury),
"quadstealth" -> MakeVehicle(quadstealth),
@ -50,7 +44,7 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition
* key - an identification string sent by the client
* value - a curried function that builds the object
*/
protected val flight1Vehicles : Map[String, ()=>Vehicle] = Map(
val flight1Vehicles : Map[String, ()=>Vehicle] = Map(
"mosquito" -> MakeVehicle(mosquito),
"lightgunship" -> MakeVehicle(lightgunship),
"wasp" -> MakeVehicle(wasp),
@ -64,7 +58,7 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition
* key - an identification string sent by the client
* value - a curried function that builds the object
*/
protected val flight2Vehicles : Map[String, ()=>Vehicle] = Map(
val flight2Vehicles : Map[String, ()=>Vehicle] = Map(
"dropship" -> MakeVehicle(dropship),
"galaxy_gunship" -> MakeVehicle(galaxy_gunship),
"lodestar" -> MakeVehicle(lodestar)
@ -75,7 +69,7 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition
* key - an identification string sent by the client
* value - a curried function that builds the object
*/
protected val bfrVehicles : Map[String, ()=>Vehicle] = Map(
val bfrVehicles : Map[String, ()=>Vehicle] = Map(
// "colossus_gunner" -> (()=>Unit),
// "colossus_flight" -> (()=>Unit),
// "peregrine_gunner" -> (()=>Unit),
@ -91,7 +85,7 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition
* key - an identification string sent by the client (for the vehicle)
* value - a curried function that builds the object
*/
protected val trunk : Map[String, _Loadout] = {
val trunk : Map[String, _Loadout] = {
val ammo_12mm = ShorthandAmmoBox(bullet_12mm, bullet_12mm.Capacity)
val ammo_15mm = ShorthandAmmoBox(bullet_15mm, bullet_15mm.Capacity)
val ammo_25mm = ShorthandAmmoBox(bullet_25mm, bullet_25mm.Capacity)
@ -483,26 +477,6 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition
)
}
def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
vehicles.get(msg.item_name) match {
case Some(vehicle) =>
val (weapons, inventory) = trunk.get(msg.item_name) match {
case Some(loadout : VehicleLoadout) =>
(
loadout.visible_slots.map(entry => { InventoryItem(EquipmentTerminalDefinition.BuildSimplifiedPattern(entry.item), entry.index) }),
loadout.inventory.map(entry => { InventoryItem(EquipmentTerminalDefinition.BuildSimplifiedPattern(entry.item), entry.index) })
)
case _ =>
(List.empty, List.empty)
}
Terminal.BuyVehicle(vehicle(), weapons, inventory)
case None =>
Terminal.NoDeal()
}
}
}
object VehicleTerminalDefinition {
/**
* Create a new `Vehicle` from provided `VehicleDefinition` objects.
* @param vdef the `VehicleDefinition` object

View file

@ -3,7 +3,7 @@ package net.psforever.objects.serverobject.turret
import net.psforever.objects.definition.{AmmoBoxDefinition, SeatDefinition, ToolDefinition}
import net.psforever.objects._
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
import net.psforever.objects.inventory.{Container, GridInventory}
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.mount.Mountable

View file

@ -1,8 +1,8 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.vehicles
import net.psforever.objects.{EquipmentSlot, PlanetSideGameObject}
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
import net.psforever.objects.inventory.Container
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.vehicles.{Seat => Chair}

View file

@ -45,11 +45,11 @@ object UtilityType extends Enumeration {
* Ostensibly, the purpose of the additional logic, when it is called,
* is to initialize a control `Actor` for the contained object.
* This `Actor` is expected by other logic.
* @see `Amenity.Owner`
* @see `Vehicle.LoadDefinition`
* @see `VehicleDefinition.Utilities`
* @param util the type of the `Amenity` object to be created
* @param vehicle the owner of this object
* @see `Amenity.Owner`
*/
class Utility(util : UtilityType.Value, vehicle : Vehicle) {
private val obj : Amenity = Utility.BuildUtilityFunc(util)
@ -154,9 +154,8 @@ object Utility {
* The `Terminal` `Utility` produced has proximity effects.
* @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
*/
class ProximityTerminalUtility(tdef : TerminalDefinition) extends Terminal(tdef)
class ProximityTerminalUtility(tdef : ProximityTerminalDefinition) extends ProximityTerminal(tdef)
with UtilityWorldEntity
with ProximityUnit
/**
* Override for a `Terminal` object so that it inherits the spatial characteristics of its `Owner`.
@ -166,19 +165,26 @@ object Utility {
*/
class TeleportPadTerminalUtility(tdef : TerminalDefinition) extends TerminalUtility(tdef) {
/**
* na
* @param player na
* @param msg na
* @return na
* This kind of `Terminal` object only produces one object of importance - a Router's telepad unit.
* When this `Telepad` object is produced, it shlould be associated with the Router,
* that is, with the owner of the `Terminal` object.
* @param player the player who made the request
* @param msg the request message
* @return a message that resolves the transaction
*/
override def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = {
val reply = super.Buy(player, msg)
reply match {
case Terminal.BuyEquipment(obj : Telepad) =>
obj.Router = Owner.GUID
case _ => ;
override def Request(player : Player, msg : Any) : Terminal.Exchange = {
msg match {
case message : ItemTransactionMessage =>
val reply = super.Request(player, message)
reply match {
case Terminal.BuyEquipment(obj : Telepad) =>
obj.Router = Owner.GUID
case _ => ;
}
reply
case _ =>
Terminal.NoDeal()
}
reply
}
}
@ -218,22 +224,20 @@ object Utility {
case UtilityType.ams_respawn_tube =>
SpawnTubeDefinition.Setup
case UtilityType.bfr_rearm_terminal =>
_OrderTerminalDefinition.Setup
OrderTerminalDefinition.Setup
case UtilityType.lodestar_repair_terminal =>
ProximityTerminal.Setup
case UtilityType.matrix_terminalc =>
MatrixTerminalDefinition.Setup
case UtilityType.multivehicle_rearm_terminal =>
_OrderTerminalDefinition.Setup
OrderTerminalDefinition.Setup
case UtilityType.order_terminala =>
OrderTerminalABDefinition.Setup
OrderTerminalDefinition.Setup
case UtilityType.order_terminalb =>
OrderTerminalABDefinition.Setup
OrderTerminalDefinition.Setup
case UtilityType.teleportpad_terminal =>
TeleportPadTerminalDefinition.Setup
OrderTerminalDefinition.Setup
case UtilityType.internal_router_telepad_deployable =>
TelepadLike.Setup
}
//private def defaultSetup(o1 : Amenity, o2 : ActorContext) : Unit = { }
}

View file

@ -1,8 +1,9 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.vital.resistance
import net.psforever.objects.{ExoSuitDefinition, GlobalDefinitions}
import net.psforever.objects.GlobalDefinitions
import net.psforever.objects.ballistics._
import net.psforever.objects.definition.ExoSuitDefinition
import net.psforever.objects.vital.projectile.ProjectileCalculations
import net.psforever.types.ExoSuitType

View file

@ -236,10 +236,10 @@ object PacketHelpers {
* A `peek` that decodes like the normal but encodes nothing.
* Decoding `Codec[A]` from the input vector emits a value but reverts to the prior read position.
* Encoding `Codec[A]` to the input vector appends no new data to the input vector.
* In effect, `peek` is a harmless meta-`Codec` that introduces no changes to the input vector.
* In effect, `peek` is a harmless meta-`Codec` that processes a value and introduces no changes to the input/output vector.
* @see `scodec.codecs.peek` or `codecs/package.scala:peek`
* @param target codec that decodes the value
* @return codec that behaves the same as `target` but resets remainder to the input vector
* @return `Codec` that behaves the same as `target` but resets the contents of the vector as if `Codec` were never applied
*/
def peek[A](target: Codec[A]): Codec[A] = new Codec[A] {
def sizeBound = target.sizeBound

View file

@ -41,10 +41,22 @@ final case class DeployRequestMessage(player_guid : PlanetSideGUID,
}
object DeployRequestMessage extends Marshallable[DeployRequestMessage] {
private val driveState3u = uint(3).xmap[DriveState.Value] (
n => DriveState(n),
n => {
if(n.id > 7) {
0
}
else {
n.id
}
}
)
implicit val codec : Codec[DeployRequestMessage] = (
("player_guid" | PlanetSideGUID.codec) ::
("vehicle_guid" | PlanetSideGUID.codec) ::
("deploy_state" | DriveState.codec) ::
("deploy_state" | driveState3u) ::
("unk2" | uint(5)) ::
("unk3" | bool) ::
("pos" | Vector3.codec_pos)

View file

@ -39,7 +39,7 @@ final case class ObjectCreateDetailedMessage(streamLength : Long,
objectClass : Int,
guid : PlanetSideGUID,
parentInfo : Option[ObjectCreateMessageParent],
data : Option[ConstructorData])
data : ConstructorData)
extends PlanetSideGamePacket {
type Packet = ObjectCreateDetailedMessage
def opcode = GamePacketOpcode.ObjectCreateMessage
@ -57,7 +57,7 @@ object ObjectCreateDetailedMessage extends Marshallable[ObjectCreateDetailedMess
*/
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))
ObjectCreateDetailedMessage(ObjectCreateBase.streamLen(parentInfoOpt, data), objectClass, guid, parentInfoOpt, data)
}
/**
@ -68,7 +68,7 @@ object ObjectCreateDetailedMessage extends Marshallable[ObjectCreateDetailedMess
* @return an ObjectCreateMessage
*/
def apply(objectClass : Int, guid : PlanetSideGUID, data : ConstructorData) : ObjectCreateDetailedMessage = {
ObjectCreateDetailedMessage(ObjectCreateBase.streamLen(None, data), objectClass, guid, None, Some(data))
ObjectCreateDetailedMessage(ObjectCreateBase.streamLen(None, data), objectClass, guid, None, data)
}
implicit val codec : Codec[ObjectCreateDetailedMessage] = ObjectCreateBase.baseCodec.exmap[ObjectCreateDetailedMessage] (
@ -77,31 +77,36 @@ object ObjectCreateDetailedMessage extends Marshallable[ObjectCreateDetailedMess
Attempt.failure(Err("no data to decode"))
case len :: cls :: guid :: par :: data :: HNil =>
val obj = ObjectCreateBase.decodeData(cls, data,
ObjectCreateBase.decodeData(cls, data,
if(par.isDefined) {
ObjectClass.selectDataDetailedCodec
}
else {
ObjectClass.selectDataDroppedDetailedCodec
}
)
Attempt.successful(ObjectCreateDetailedMessage(len, cls, guid, par, obj))
) match {
case Attempt.Successful(obj) =>
Attempt.successful(ObjectCreateDetailedMessage(len, cls, guid, par, obj))
case Attempt.Failure(err) =>
Attempt.failure(err)
}
},
{
case ObjectCreateDetailedMessage(_ , _ , _, _, None) =>
Attempt.failure(Err("no object to encode"))
case ObjectCreateDetailedMessage(_, cls, guid, par, Some(obj)) =>
case ObjectCreateDetailedMessage(_, cls, guid, par, obj) =>
val len = ObjectCreateBase.streamLen(par, obj) //even if a stream length has been assigned, it can not be trusted during encoding
val bitvec = ObjectCreateBase.encodeData(cls, obj,
ObjectCreateBase.encodeData(cls, obj,
if(par.isDefined) {
ObjectClass.selectDataDetailedCodec
}
else {
ObjectClass.selectDataDroppedDetailedCodec
}
)
Attempt.successful(len :: cls :: guid :: par :: bitvec :: HNil)
) match {
case Attempt.Successful(bvec) =>
Attempt.successful(len :: cls :: guid :: par :: bvec :: HNil)
case Attempt.Failure(err) =>
Attempt.failure(err)
}
}
)
}

View file

@ -2,7 +2,7 @@
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import net.psforever.packet.game.objectcreate._
import net.psforever.packet.game.objectcreate.{ObjectCreateBase, _}
import scodec.{Attempt, Codec, Err}
import scodec.bits.BitVector
import shapeless.{::, HNil}
@ -50,7 +50,7 @@ final case class ObjectCreateMessage(streamLength : Long,
objectClass : Int,
guid : PlanetSideGUID,
parentInfo : Option[ObjectCreateMessageParent],
data : Option[ConstructorData])
data : ConstructorData)
extends PlanetSideGamePacket {
type Packet = ObjectCreateMessage
def opcode = GamePacketOpcode.ObjectCreateMessage_Duplicate
@ -68,7 +68,7 @@ object ObjectCreateMessage extends Marshallable[ObjectCreateMessage] {
*/
def apply(objectClass : Int, guid : PlanetSideGUID, parentInfo : ObjectCreateMessageParent, data : ConstructorData) : ObjectCreateMessage = {
val parentInfoOpt : Option[ObjectCreateMessageParent] = Some(parentInfo)
ObjectCreateMessage(ObjectCreateBase.streamLen(parentInfoOpt, data), objectClass, guid, parentInfoOpt, Some(data))
ObjectCreateMessage(ObjectCreateBase.streamLen(parentInfoOpt, data), objectClass, guid, parentInfoOpt, data)
}
/**
@ -79,7 +79,7 @@ object ObjectCreateMessage extends Marshallable[ObjectCreateMessage] {
* @return an `ObjectCreateMessage`
*/
def apply(objectClass : Int, guid : PlanetSideGUID, data : ConstructorData) : ObjectCreateMessage = {
ObjectCreateMessage(ObjectCreateBase.streamLen(None, data), objectClass, guid, None, Some(data))
ObjectCreateMessage(ObjectCreateBase.streamLen(None, data), objectClass, guid, None, data)
}
implicit val codec : Codec[ObjectCreateMessage] = ObjectCreateBase.baseCodec.exmap[ObjectCreateMessage] (
@ -88,31 +88,35 @@ object ObjectCreateMessage extends Marshallable[ObjectCreateMessage] {
Attempt.failure(Err("no data to decode"))
case len :: cls :: guid :: par :: data :: HNil =>
val obj = ObjectCreateBase.decodeData(cls, data,
if(par.isDefined) {
ObjectClass.selectDataCodec
ObjectCreateBase.decodeData(cls, data, if(par.isDefined) {
ObjectClass.selectDataCodec
}
else {
ObjectClass.selectDataDroppedCodec
}
)
Attempt.successful(ObjectCreateMessage(len, cls, guid, par, obj))
) match {
case Attempt.Successful(obj) =>
Attempt.successful(ObjectCreateMessage(len, cls, guid, par, obj))
case Attempt.Failure(err) =>
Attempt.failure(err)
}
},
{
case ObjectCreateMessage(_ , _ , _, _, None) =>
Attempt.failure(Err("no object to encode"))
case ObjectCreateMessage(_, cls, guid, par, Some(obj)) =>
case ObjectCreateMessage(_, cls, guid, par, obj) =>
val len = ObjectCreateBase.streamLen(par, obj) //even if a stream length has been assigned, it can not be trusted during encoding
val bitvec = ObjectCreateBase.encodeData(cls, obj,
ObjectCreateBase.encodeData(cls, obj,
if(par.isDefined) {
ObjectClass.selectDataCodec
}
else {
ObjectClass.selectDataDroppedCodec
}
)
Attempt.successful(len :: cls :: guid :: par :: bitvec :: HNil)
) match {
case Attempt.Successful(bvec) =>
Attempt.successful(len :: cls :: guid :: par :: bvec :: HNil)
case Attempt.Failure(err) =>
Attempt.failure(err)
}
}
)
}

View file

@ -1,43 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
/**
* A representation of an adaptive construction engine (ACE).
* This one-time-use item deploys a variety of utilities into the game environment.
* Has an advanced version internally called an `advanced_ace` and commonly called a Field Deployment Unit (FDU).
* @param unk1 na
* @param unk2 na
* @param unk3 na
*/
final case class ACEData(unk1 : Int,
unk2 : Int,
unk3 : Int = 0
) extends ConstructorData {
override def bitsize : Long = 34L
}
object ACEData extends Marshallable[ACEData] {
implicit val codec : Codec[ACEData] = (
("unk1" | uint4L) ::
("unk2" | uint4L) ::
uint(20) ::
("unk3" | uint4L) ::
uint2L
).exmap[ACEData] (
{
case unk1 :: unk2 :: 0 :: unk3 :: 0 :: HNil =>
Attempt.successful(ACEData(unk1, unk2, unk3))
case _ :: _ :: _ :: _ :: _ :: HNil =>
Attempt.failure(Err("invalid ace data format"))
},
{
case ACEData(unk1, unk2, unk3) =>
Attempt.successful(unk1 :: unk2 :: 0 :: unk3 :: 0 :: HNil)
}
)
}

View file

@ -11,7 +11,7 @@ import shapeless.{::, HNil}
* @param deploy data common to objects spawned by the (advanced) adaptive construction engine
* @param health the amount of health the object has, as a percentage of a filled bar
*/
final case class AegisShieldGeneratorData(deploy : CommonFieldData,
final case class AegisShieldGeneratorData(deploy : CommonFieldDataWithPlacement,
health : Int
) extends ConstructorData {
override def bitsize : Long = {
@ -21,15 +21,16 @@ final case class AegisShieldGeneratorData(deploy : CommonFieldData,
object AegisShieldGeneratorData extends Marshallable[AegisShieldGeneratorData] {
implicit val codec : Codec[AegisShieldGeneratorData] = (
("deploy" | CommonFieldData.codec) ::
("deploy" | CommonFieldDataWithPlacement.codec) ::
("health" | uint8L) ::
uint32 :: uint32 :: uint32 :: uint4L //100 bits
).exmap[AegisShieldGeneratorData] (
{
case deploy :: health :: 0 :: 0 :: 0 :: 0 :: HNil =>
Attempt.successful(AegisShieldGeneratorData(deploy, health))
case _ =>
Attempt.failure(Err("invalid aegis data format"))
case data =>
Attempt.failure(Err(s"invalid aegis data format - $data"))
},
{
case AegisShieldGeneratorData(deploy, health) =>

View file

@ -1,53 +1,26 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
import net.psforever.packet.game.PlanetSideGUID
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
/**
* A representation of ammunition that can be created using `ObjectCreateMessage` packet data.
* This data will help construct a "box" of that type of ammunition when standalone.
* It can also be constructed directly inside a weapon as its magazine.<br>
* <br>
* This ammunition object ompletely ignores thr capacity field, normal to detailed ammunition objects.
* Creating an object of this type directly and picking it up or observing it (in a weapon) reveals a single round.
* @param unk na;
* defaults to 0
* This ammunition object ompletely ignores the capacity field, normal to detailed ammunition objects.
* Creating an object of this type directly and picking it up or observing it (in a weapon) will reveals single round.
* @see `DetailedAmmoBoxData`
*/
final case class AmmoBoxData(unk : Int = 0) extends ConstructorData {
override def bitsize : Long = 24L
}
object AmmoBoxData extends Marshallable[AmmoBoxData] {
object AmmoBoxData {
/**
* An abbreviated constructor for creating `AmmoBoxData` while masking use of `InternalSlot`.
* @param cls the code for the type of object being constructed
* @param guid the GUID this object will be assigned
* @param parentSlot a parent-defined slot identifier that explains where the child is to be attached to the parent
* @param ammo the ammunition object
* @return an `InternalSlot` object that encapsulates `AmmoBoxData`
* @return an `InternalSlot` object that encapsulates `CommonFieldData`
*/
def apply(cls : Int, guid : PlanetSideGUID, parentSlot : Int, ammo : AmmoBoxData) : InternalSlot =
def apply(cls : Int, guid : PlanetSideGUID, parentSlot : Int, ammo : CommonFieldData) : InternalSlot =
new InternalSlot(cls, guid, parentSlot, ammo)
implicit val codec : Codec[AmmoBoxData] = (
uint4L ::
("unk" | uint4L) :: // 8 - common - 4 - safe, 2 - stream misalignment, 1 - safe, 0 - common
uint(16)
).exmap[AmmoBoxData] (
{
case 0xC :: unk :: 0 :: HNil =>
Attempt.successful(AmmoBoxData(unk))
case _ :: _ :: _ :: HNil =>
Attempt.failure(Err("invalid ammunition data format"))
},
{
case AmmoBoxData(unk) =>
Attempt.successful(0xC :: unk :: 0 :: HNil)
}
)
}

View file

@ -1,34 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
/**
* A representation of the detonator utility that is created when putting down a Boomer with an ACE.
* @param unk na
*/
final case class BoomerTriggerData(unk : Int = 0x8) extends ConstructorData {
override def bitsize : Long = 34L
}
object BoomerTriggerData extends Marshallable[BoomerTriggerData] {
implicit val codec : Codec[BoomerTriggerData] = (
uint4L ::
uint4L ::
uint(26)
).exmap[BoomerTriggerData] (
{
case 0xC :: unk :: 0 :: HNil =>
Attempt.successful(BoomerTriggerData(unk))
case _ =>
Attempt.failure(Err("invalid command detonater format"))
},
{
case BoomerTriggerData(unk) =>
Attempt.successful(0xC :: unk :: 0 :: HNil)
}
)
}

View file

@ -50,8 +50,9 @@ object CaptureFlagData extends Marshallable[CaptureFlagData] {
{
case pos :: fac :: false :: 4 :: 0 :: unk1 :: 0 :: unk2 :: 0 :: unk3 :: unk4 :: 0 :: HNil =>
Attempt.Successful(CaptureFlagData(pos, fac, unk1, unk2, unk3, unk4))
case _ =>
Attempt.failure(Err("invalid capture flag data"))
case data =>
Attempt.failure(Err(s"invalid capture flag data format - $data"))
},
{
case CaptureFlagData(pos, fac, unk1, unk2, unk3, unk4) =>

View file

@ -1,6 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.{Marshallable, PacketHelpers}
import net.psforever.types._
import scodec.{Attempt, Codec, Err}
@ -12,22 +13,20 @@ import shapeless.{::, HNil}
* @see `CharacterData`
* @see `DetailedCharacterData`
* @see `ExoSuitType`
* @param app the player's cardinal appearance settings
* @param black_ops whether or not this avatar is enrolled in Black OPs
* @param jammered the player has been caught in an EMP blast recently;
* creates a jammered sound effect that follows the player around and can be heard by others
* @param exosuit the type of exo-suit the avatar will be depicted in;
* for Black OPs, the agile exo-suit and the reinforced exo-suit are replaced with the Black OPs exo-suits
* @param app the player's cardinal appearance settings
* @param data common field data<br>
* -bops - this vehicle belongs to the Black Ops, regardless of the faction field;
* activates the green camo and adjusts permissions<br>
* -destroyed - flagged when using a model that is not the standard player in some stance<br>
* -jammered - the player has been caught in an EMP blast recently;
* creates a jammered sound effect that follows the player around and can be heard by others<br>
* -player_guid - does nothing?
* @param exosuit the type of exo-suit the avatar will be depicted in;
* for Black OPs, the agile exo-suit and the reinforced exo-suit are replaced with the Black OPs exo-suits
*/
final case class CharacterAppearanceA(app : BasicCharacterData,
black_ops : Boolean,
altModel : Boolean,
unk1 : Boolean,
unk2 : Option[CharacterAppearanceData.ExtraData],
jammered : Boolean,
data : CommonFieldData,
exosuit : ExoSuitType.Value,
unk3 : Option[Int],
unk4 : Int,
unk5 : Int,
unk6 : Long,
unk7 : Int,
@ -36,11 +35,9 @@ final case class CharacterAppearanceA(app : BasicCharacterData,
unkA : Int)
(name_padding : Int) extends StreamBitSize {
override def bitsize : Long = {
//factor guard bool values into the base size, not its corresponding optional field
val unk2Size : Long = unk2 match { case Some(n) => n.bitsize ; case None => 0L }
val dataSize : Long = data.bitsize
val nameStringSize : Long = StreamBitSize.stringBitSize(app.name, 16) + name_padding
val unk3Size : Long = unk3 match { case Some(_) => 32L ; case None => 0L }
137L + unk2Size + nameStringSize + unk3Size
114L + dataSize + nameStringSize
}
}
@ -162,14 +159,23 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
val altModel : Boolean = backpack || on_zipline.isDefined
val a = CharacterAppearanceA(
app,
black_ops,
altModel,
false,
None,
jammered,
CommonFieldData(
app.faction,
black_ops,
altModel,
false,
None,
false,
None,
if(jammered) {
Some(0)
}
else {
None
},
PlanetSideGUID(0)
),
exosuit,
None,
0,
0,
0,
0,
@ -206,7 +212,7 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
def apply(a : Int=>CharacterAppearanceA, b : (Boolean,Int)=>CharacterAppearanceB, ribbons : RibbonBars)(name_padding : Int) : CharacterAppearanceData = {
val first = a(name_padding)
CharacterAppearanceData(a(name_padding), b(first.altModel, name_padding), ribbons)(name_padding)
CharacterAppearanceData(a(name_padding), b(first.data.alternate, name_padding), ribbons)(name_padding)
}
/**
@ -238,13 +244,18 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
* @return the length of the variable field that exists when using alternate models
*/
def altModelBit(app : CharacterAppearanceData) : Option[Int] = if(app.b.backpack || app.b.on_zipline.isDefined) {
Some(1)
if(!app.a.data.alternate) {
throw new IllegalArgumentException("missing alternate model flag when should be set")
}
else {
Some(1)
}
}
else {
None
}
def namePadding(inheritPad : Int, pad : Option[ExtraData]) : Int = {
def namePadding(inheritPad : Int, pad : Option[CommonFieldDataExtra]) : Int = {
pad match {
case Some(n) =>
val bitsize = n.bitsize.toInt % 8
@ -282,45 +293,48 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
* @return na
*/
def a_codec(name_padding : Int) : Codec[CharacterAppearanceA] = (
("faction" | PlanetSideEmpire.codec) ::
("black_ops" | bool) ::
(("alt_model" | bool) >>:~ { alt_model => //modifies stream format (to display alternate player models)
("unk1" | bool) :: //serves a different internal purpose depending on the state of alt_model
(conditional(false, "unk2" | extra_codec) >>:~ { extra => //TODO not sure what causes this branch
("jammered" | bool) ::
optional(bool, "unk3" | uint16L) ::
("unk4" | uint16L) ::
("name" | PacketHelpers.encodedWideStringAligned(namePadding(name_padding, extra))) ::
("exosuit" | ExoSuitType.codec) ::
("unk5" | uint2) :: //unknown
("sex" | CharacterGender.codec) ::
("head" | uint8L) ::
("voice" | CharacterVoice.codec) ::
("unk6" | uint32L) ::
("unk7" | uint16L) ::
("unk8" | uint16L) ::
("unk9" | uint16L) ::
("unkA" | uint16L) //usually either 0 or 65535
})
})
("data" | CommonFieldData.codec) >>:~ { data =>
("name" | PacketHelpers.encodedWideStringAligned(namePadding(name_padding, data.v2))) ::
("exosuit" | ExoSuitType.codec) ::
("unk5" | uint2) :: //unknown
("sex" | CharacterGender.codec) ::
("head" | uint8L) ::
("voice" | CharacterVoice.codec) ::
("unk6" | uint32L) ::
("unk7" | uint16L) ::
("unk8" | uint16L) ::
("unk9" | uint16L) ::
("unkA" | uint16L) //usually either 0 or 65535
}
).exmap[CharacterAppearanceA] (
{
case faction :: bops :: alt :: u1 :: u2 :: jamd :: u3 :: u4 :: name :: suit :: u5 :: sex :: head :: v1 :: u6 :: u7 :: u8 :: u9 :: uA :: HNil =>
case data :: name :: suit :: u5 :: sex :: head :: v1 :: u6 :: u7 :: u8 :: u9 :: uA :: HNil =>
Attempt.successful(
CharacterAppearanceA(BasicCharacterData(name, faction, sex, head, v1), bops, alt, u1, u2, jamd, suit, u3, u4, u5, u6, u7, u8, u9, uA)(name_padding)
CharacterAppearanceA(BasicCharacterData(name, data.faction, sex, head, v1), data, suit, u5, u6, u7, u8, u9, uA)(name_padding)
)
case _ =>
Attempt.Failure(Err("invalid character appearance data; can not encode"))
},
{
case CharacterAppearanceA(BasicCharacterData(name, PlanetSideEmpire.NEUTRAL, _, _, _), _, _, _, _, _, _, _, _, _, _, _, _, _, _) =>
case CharacterAppearanceA(BasicCharacterData(name, PlanetSideEmpire.NEUTRAL, _, _, _), _, _, _, _, _, _, _, _) =>
Attempt.failure(Err(s"character $name's faction can not declare as neutral"))
case CharacterAppearanceA(BasicCharacterData(name, faction, sex, head, v1), bops, alt, u1, u2, jamd, suit, u3, u4, u5, u6, u7, u8, u9, uA) =>
Attempt.successful(
faction :: bops :: alt :: u1 :: u2 :: jamd :: u3 :: u4 :: name :: suit :: u5 :: sex :: head :: v1 :: u6 :: u7 :: u8 :: u9 :: uA :: HNil
)
case CharacterAppearanceA(BasicCharacterData(name, faction, sex, head, v1), data, suit, u5, u6, u7, u8, u9, uA) =>
if(faction != data.faction) {
Attempt.failure(Err(s"character $name's faction fields are mismatched, $faction != ${data.faction}"))
}
else if(data.faction == PlanetSideEmpire.NEUTRAL) {
Attempt.successful(
CommonFieldData(faction, data.bops, data.alternate, data.v1, data.v2, data.v3, None, data.v5, PlanetSideGUID(0)) ::
name :: suit :: u5 :: sex :: head :: v1 :: u6 :: u7 :: u8 :: u9 :: uA :: HNil
)
}
else {
Attempt.successful(
data :: name :: suit :: u5 :: sex :: head :: v1 :: u6 :: u7 :: u8 :: u9 :: uA :: HNil
)
}
case _ =>
Attempt.Failure(Err("invalid character appearance data; can not decode"))
@ -385,7 +399,7 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
def codec(name_padding : Int) : Codec[CharacterAppearanceData] = (
("a" | a_codec(name_padding)) >>:~ { a =>
("b" | b_codec(a.altModel, name_padding)) ::
("b" | b_codec(a.data.alternate, name_padding)) ::
("ribbons" | RibbonBars.codec)
}
).xmap[CharacterAppearanceData] (

View file

@ -27,7 +27,8 @@ object ImplantEffects extends Enumeration {
/**
* Values for the four different color designs that impact a player's uniform.
* Exo-suits get minor graphical updates at the following battle rank levels: seven, fourteen, and twenty-five.
* Exo-suits get minor graphical updates at the following battle rank levels: seven (1), fourteen (2), and twenty-five (4).
* The values 3 and 5 also exist and are visually descriptive to the third upgrade.
*/
object UniformStyle extends Enumeration {
type Type = Value
@ -35,9 +36,11 @@ object UniformStyle extends Enumeration {
val Normal = Value(0)
val FirstUpgrade = Value(1)
val SecondUpgrade = Value(2)
val SecondUpgradeEx = Value(3)
val ThirdUpgrade = Value(4)
val ThirdUpgradeEx = Value(5)
implicit val codec = PacketHelpers.createEnumerationCodec(this, uintL(3))
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint(3))
}
/**
@ -115,7 +118,7 @@ object CharacterData extends Marshallable[CharacterData] {
uint(3) :: //uniform_upgrade is actually interpreted as a 6u field, but the lower 3u seems to be discarded
("command_rank" | uintL(3)) ::
listOfN(uint2, "implant_effects" | ImplantEffects.codec) ::
conditional(style == UniformStyle.ThirdUpgrade, "cosmetics" | Cosmetics.codec)
conditional(style.id > UniformStyle.SecondUpgrade.id,"cosmetics" | Cosmetics.codec)
})
).exmap[CharacterData] (
{
@ -141,7 +144,7 @@ object CharacterData extends Marshallable[CharacterData] {
uint(3) :: //uniform_upgrade is actually interpreted as a 6u field, but the lower 3u seems to be discarded
("command_rank" | uintL(3)) ::
listOfN(uint2, "implant_effects" | ImplantEffects.codec) ::
conditional(style == UniformStyle.ThirdUpgrade, "cosmetics" | Cosmetics.codec)
conditional(style.id > UniformStyle.SecondUpgrade.id, "cosmetics" | Cosmetics.codec)
}
).exmap[CharacterData] (
{

View file

@ -1,35 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
/**
* A representation of the command uplink device.<br>
* I don't know much about the command uplink device so someone else has to provide this commentary.
*/
final case class CommandDetonaterData(unk1 : Int = 0,
unk2 : Int = 0) extends ConstructorData {
override def bitsize : Long = 34L
}
object CommandDetonaterData extends Marshallable[CommandDetonaterData] {
implicit val codec : Codec[CommandDetonaterData] = (
("unk1" | uint4L) ::
("unk2" | uint4L) ::
uint(26)
).exmap[CommandDetonaterData] (
{
case unk1 :: unk2 :: 0 :: HNil =>
Attempt.successful(CommandDetonaterData(unk1, unk2))
case _ :: _ :: _ :: HNil =>
Attempt.failure(Err("invalid command detonator data format"))
},
{
case CommandDetonaterData(unk1, unk2) =>
Attempt.successful(unk1 :: unk2 :: 0 :: HNil)
}
)
}

View file

@ -8,96 +8,138 @@ import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
final case class CommonFieldDataExtra(unk1 : Int, unk2 : Boolean) extends StreamBitSize {
override def bitsize : Long = 17L
}
object CommonFieldDataExtra {
implicit val codec : Codec[CommonFieldDataExtra] = (
("unk1" | uint16L) ::
("unk2" | bool)
).as[CommonFieldDataExtra]
}
/**
* Data that is common to a number of game object serializations.
* @param pos where and how the object is oriented
* @param faction association of the object with
* @param unk na
* @param player_guid the player who placed/leverages/[action]s this object
* @param faction faction affinity
* `NEUTRAL` when not required to be any specific value
* @param bops usually indicates black ops affiliation
* @param alternate usually indicates variance in model from default (e.g., vehicle is destroyed, player has released, etc.);
* when set on a tool, that tool will be rendered nonfunctional instead (though it can still be equipped)
* @param v1 na
* @param v2 na;
* optional data whose reading is triggered in unknown conditions;
* flag a weapon as "jammered"
* @param v3 na;
* for weapons, works like `alternate`
* @param v4 na;
* a field used by a second encoding format for this data
* @param v5 na;
* previously considered to flag as "jammered"
* @param guid usually indicates another active game object that placed/leverages/[action]s this object
*/
final case class CommonFieldData(pos : PlacementData,
faction : PlanetSideEmpire.Value,
final case class CommonFieldData(faction : PlanetSideEmpire.Value,
bops : Boolean,
destroyed : Boolean,
unk : Int,
jammered : Boolean,
player_guid : PlanetSideGUID
) extends StreamBitSize {
override def bitsize : Long = 23L + pos.bitsize
alternate : Boolean,
v1 : Boolean,
v2 : Option[CommonFieldDataExtra],
v3 : Boolean,
v4 : Option[Boolean],
v5 : Option[Int],
guid : PlanetSideGUID
) extends ConstructorData {
override def bitsize : Long = {
val extraSize : Long = v2 match {
case Some(v) => v.bitsize
case None => 0L
}
val v4Size = v4 match {
case Some(_) => 1L
case None => 0L
}
val v5Size = v5 match {
case Some(_) => 16L
case None => 0L
}
23L + extraSize + v4Size + v5Size
}
def apply(flag : Boolean) : CommonFieldData = CommonFieldData(faction, bops, alternate, v1, v2, v3, Some(flag), v5, guid)
}
object CommonFieldData extends Marshallable[CommonFieldData] {
final val internalWeapon_bitsize : Long = 10
/**
* Overloaded constructor that eliminates the need to list the fourth, optional, GUID field.
* @param pos where and how the object is oriented
* @param faction association of the object with
* @param unk na
* Overloaded constructors.
* @return a `CommonFieldData` object
*/
def apply(pos : PlacementData, faction : PlanetSideEmpire.Value, unk : Int) : CommonFieldData =
CommonFieldData(pos, faction, false, false, unk, false, PlanetSideGUID(0))
def apply() : CommonFieldData =
CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, false, None, false, None, None, PlanetSideGUID(0))
def apply(pos : PlacementData, faction : PlanetSideEmpire.Value, unk : Int, player_guid : PlanetSideGUID) : CommonFieldData =
CommonFieldData(pos, faction, false, false, unk, false, player_guid)
def apply(faction : PlanetSideEmpire.Value) : CommonFieldData =
CommonFieldData(faction, false, false, false, None, false, None, None, PlanetSideGUID(0))
def apply(pos : PlacementData, faction : PlanetSideEmpire.Value, destroyed : Boolean, unk : Int) : CommonFieldData =
CommonFieldData(pos, faction, false, destroyed, unk, false, PlanetSideGUID(0))
def apply(faction : PlanetSideEmpire.Value, unk : Int) : CommonFieldData =
CommonFieldData(faction, false, false, unk>1, None, unk%1==1, None, None, PlanetSideGUID(0))
def apply(pos : PlacementData, faction : PlanetSideEmpire.Value, destroyed : Boolean, unk : Int, player_guid : PlanetSideGUID) : CommonFieldData =
CommonFieldData(pos, faction, false, destroyed, unk, false, player_guid)
def apply(faction : PlanetSideEmpire.Value, unk : Int, player_guid : PlanetSideGUID) : CommonFieldData =
CommonFieldData(faction, false, false, unk>1, None, unk%1==1, None, None, player_guid)
/**
* `Codec` for transforming reliable `WeaponData` from the internal structure of the turret when it is defined.
* Works for both `SmallTurretData` and `OneMannedFieldTurretData`.
*/
val internalWeaponCodec : Codec[InternalSlot] = (
uint8L :: //number of internal weapons (should be 1)?
uint2L ::
InternalSlot.codec
).exmap[InternalSlot] (
def apply(faction : PlanetSideEmpire.Value, destroyed : Boolean, unk : Int) : CommonFieldData =
CommonFieldData(faction, false, destroyed, unk>1, None, unk%1==1, None, None, PlanetSideGUID(0))
def apply(faction : PlanetSideEmpire.Value, destroyed : Boolean, unk : Int, player_guid : PlanetSideGUID) : CommonFieldData =
CommonFieldData(faction, false, destroyed, unk>1, None, unk%1==1, None, None, player_guid)
def apply(faction : PlanetSideEmpire.Value, bops : Boolean, destroyed : Boolean, unk : Int, jammered : Boolean, player_guid : PlanetSideGUID) : CommonFieldData = {
val jammeredField = if(jammered) { Some(0) } else { None }
CommonFieldData(faction, bops, destroyed, unk>1, None, unk%1==1, None, jammeredField, player_guid)
}
def codec(extra : Boolean) : Codec[CommonFieldData] = (
("faction" | PlanetSideEmpire.codec) ::
("bops" | bool) ::
("alternate" | bool) ::
("v1" | bool) :: //the purpose of this bit changes depending on the previous bit
conditional(extra, "v2" | CommonFieldDataExtra.codec) ::
("v3" | bool) ::
optional(bool, "v5" | uint16L) ::
("guid" | PlanetSideGUID.codec)
).xmap[CommonFieldData] (
{
case 1 :: 0 :: InternalSlot(a1, b1, c1, WeaponData(a2, b2, c2, d)) :: HNil =>
Attempt.successful(InternalSlot(a1, b1, c1, WeaponData(a2, b2, c2, d)))
case 1 :: 0 :: InternalSlot(_, _, _, _) :: HNil =>
Attempt.failure(Err(s"turret internals must contain weapon data"))
case n :: 0 :: _ :: HNil =>
Attempt.failure(Err(s"turret internals can not have $n weapons"))
case _ =>
Attempt.failure(Err("invalid turret internals data format"))
case faction :: bops :: alternate :: v1 :: v2 :: v3 :: v5 :: player_guid :: HNil =>
CommonFieldData(faction, bops, alternate, v1, v2, v3, None, v5, player_guid)
},
{
case InternalSlot(a1, b1, c1, WeaponData(a2, b2, c2, d)) =>
Attempt.successful(1 :: 0 :: InternalSlot(a1, b1, c1, WeaponData(a2, b2, c2, d)) :: HNil)
case InternalSlot(_, _, _, _) =>
Attempt.failure(Err(s"turret internals must contain weapon data"))
case _ =>
Attempt.failure(Err("invalid turret internals data format"))
case CommonFieldData(faction, bops, alternate, v1, v2, v3, _, v5, guid) =>
faction :: bops :: alternate :: v1 :: v2 :: v3 :: v5 :: guid :: HNil
}
)
implicit val codec : Codec[CommonFieldData] = (
("pos" | PlacementData.codec) ::
("faction" | PlanetSideEmpire.codec) ::
implicit val codec : Codec[CommonFieldData] = codec(false)
def codec2(extra : Boolean) : Codec[CommonFieldData] = (
("faction" | PlanetSideEmpire.codec) ::
("bops" | bool) ::
("destroyed" | bool) ::
("unk" | uint2L) :: //3 - na, 2 - common, 1 - na, 0 - common?
("jammered" | bool) ::
("player_guid" | PlanetSideGUID.codec)
("alternate" | bool) ::
("v1" | bool) :: //though the code path differs depending on the previous bit, this one gets read one way or another
conditional(extra, "v2" | CommonFieldDataExtra.codec) ::
("v3" | bool) ::
optional(bool, "v5" | uint16L) ::
("v4" | bool) ::
("guid" | PlanetSideGUID.codec)
).exmap[CommonFieldData] (
{
case pos :: fac :: bops :: wrecked :: unk :: jammered :: player :: HNil =>
Attempt.successful(CommonFieldData(pos, fac, bops, wrecked, unk,jammered, player))
case faction :: bops :: alternate :: v1 :: v2 :: v3 :: v5 :: v4 :: guid :: HNil =>
Attempt.successful(CommonFieldData(faction, bops, alternate, v1, v2, v3, Some(v4), v5, guid))
},
{
case CommonFieldData(pos, fac, bops, wrecked, unk, jammered, player) =>
Attempt.successful(pos :: fac :: bops :: wrecked :: unk :: jammered :: player :: HNil)
case CommonFieldData(_, _, _, _, _, _, None, _, _) =>
Attempt.Failure(Err("invalid CommonFieldData - expected a field to be defined, but it was 'None'"))
case CommonFieldData(faction, bops, alternate, v1, v2, v3, Some(v4), v5, player_guid) =>
Attempt.successful(faction :: bops :: alternate :: v1 :: v2 :: v3 :: v5 :: v4 :: player_guid :: HNil)
}
)
val codec2 : Codec[CommonFieldData] = codec2(false)
}

View file

@ -0,0 +1,55 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.PlanetSideEmpire
import scodec.Codec
import scodec.codecs._
/**
* Data that is common to a number of game object serializations, plus position information
* @see `DroppedItemData`
* @param pos the location, orientation, and potential velocity of the object
* @param data the common fields
*/
final case class CommonFieldDataWithPlacement(pos : PlacementData,
data : CommonFieldData
) extends ConstructorData {
override def bitsize : Long = pos.bitsize + data.bitsize
}
object CommonFieldDataWithPlacement extends Marshallable[CommonFieldDataWithPlacement] {
/**
* Overloaded constructors.
* @return a `CommonFieldDataWithPlacement` object
*/
def apply(pos : PlacementData, faction : PlanetSideEmpire.Value) : CommonFieldDataWithPlacement =
CommonFieldDataWithPlacement(pos, CommonFieldData(faction))
def apply(pos : PlacementData, faction : PlanetSideEmpire.Value, unk : Int) : CommonFieldDataWithPlacement =
CommonFieldDataWithPlacement(pos, CommonFieldData(faction, unk))
def apply(pos : PlacementData, faction : PlanetSideEmpire.Value, unk : Int, player_guid : PlanetSideGUID) : CommonFieldDataWithPlacement =
CommonFieldDataWithPlacement(pos, CommonFieldData(faction, unk, player_guid))
def apply(pos : PlacementData, faction : PlanetSideEmpire.Value, destroyed : Boolean, unk : Int) : CommonFieldDataWithPlacement =
CommonFieldDataWithPlacement(pos, CommonFieldData(faction, destroyed, unk))
def apply(pos : PlacementData, faction : PlanetSideEmpire.Value, destroyed : Boolean, unk : Int, player_guid : PlanetSideGUID) : CommonFieldDataWithPlacement =
CommonFieldDataWithPlacement(pos, CommonFieldData(faction, destroyed, unk, player_guid))
def codec(extra : Boolean) : Codec[CommonFieldDataWithPlacement] = (
("pos" | PlacementData.codec) ::
CommonFieldData.codec(extra)
).as[CommonFieldDataWithPlacement]
implicit val codec : Codec[CommonFieldDataWithPlacement] = codec(false)
def codec2(extra : Boolean) : Codec[CommonFieldDataWithPlacement] = (
("pos" | PlacementData.codec) ::
CommonFieldData.codec2(extra)
).as[CommonFieldDataWithPlacement]
implicit val codec2 : Codec[CommonFieldDataWithPlacement] = codec2(false)
}

View file

@ -1,52 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.PlanetSideEmpire
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
/**
* A representation of an object that can be interacted with when using a variety of terminals.
* This object is generally invisible.
* @param faction the faction that can access the terminal
* @param unk na
*/
final case class CommonTerminalData(faction : PlanetSideEmpire.Value,
unk : Int = 0
) extends ConstructorData {
override def bitsize : Long = 24L
}
object CommonTerminalData extends Marshallable[CommonTerminalData] {
/**
* Overloaded constructor for a type of common terminal.
* @param cls the code for the type of object being constructed
* @param guid the GUID this object will be assigned
* @param parentSlot a parent-defined slot identifier that explains where the child is to be attached to the parent
* @param terminal the `CommonTerminalData`
* @return an `InternalSlot` object
*/
def apply(cls : Int, guid : PlanetSideGUID, parentSlot : Int, terminal : CommonTerminalData) : InternalSlot =
InternalSlot(cls, guid, parentSlot, terminal)
implicit val codec : Codec[CommonTerminalData] = (
("faction" | PlanetSideEmpire.codec) ::
uint2L ::
("unk" | uint2L) ::
uint(18)
).exmap[CommonTerminalData] (
{
case fac :: 0 :: unk :: 0 :: HNil =>
Attempt.successful(CommonTerminalData(fac, unk))
case _ =>
Attempt.failure(Err("invalid terminal data format"))
},
{
case CommonTerminalData(fac, unk) =>
Attempt.successful(fac :: 0 :: unk :: 0 :: HNil)
}
)
}

View file

@ -16,30 +16,32 @@ abstract class ConstructorData extends StreamBitSize
object ConstructorData {
/**
* This pattern is intended to provide common conversion between all of the `Codec`s of the children of this class.
* The casting will be performed through use of `exmap` in the child class.
*/
type genericPattern = Option[ConstructorData]
/**
* Transform a `Codec[T]` for object type `T` into `ConstructorData.genericPattern`.
* Transform a `Codec[T]` for object type `T` into `ConstructorData`.
* @param objCodec a `Codec` that satisfies the transformation `Codec[T] -> T`
* @param objType a `String` that explains what the object should be identified as in the `Err` message;
* defaults to "object"
* @tparam T a subclass of `ConstructorData` that indicates what type the object is
* @return `ConstructorData.genericPattern`
* @return `Codec[ConstructorData]`
*/
def genericCodec[T <: ConstructorData](objCodec : Codec[T], objType : String = "object") : Codec[ConstructorData.genericPattern] =
objCodec.exmap[ConstructorData.genericPattern] (
{
case x =>
Attempt.successful(Some(x.asInstanceOf[ConstructorData]))
},
{
case Some(x) =>
Attempt.successful(x.asInstanceOf[T]) //why does this work? shouldn't type erasure be a problem?
case _ =>
Attempt.failure(Err(s"can not encode as $objType data"))
}
)
def apply[T <: ConstructorData](objCodec : Codec[T], objType : String = "object") : Codec[ConstructorData] =
objCodec.exmap[ConstructorData] (
x => {
try {
Attempt.successful(x.asInstanceOf[ConstructorData])
}
catch {
case ex : Exception =>
Attempt.failure(Err(s"can not cast decode of $x to $objType - $ex"))
}
},
x => {
try {
Attempt.successful(x.asInstanceOf[T]) //why does this work? shouldn't type erasure be a problem?
}
catch {
case ex : Exception =>
Attempt.failure(Err(s"can not cast encode $x to $objType - $ex"))
}
}
)
}

View file

@ -1,37 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
import net.psforever.packet.game.PlanetSideGUID
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
/**
* na
*/
final case class ContainedTelepadDeployableData(unk : Int,
router_guid : PlanetSideGUID) extends ConstructorData {
override def bitsize : Long = 59L
}
object ContainedTelepadDeployableData extends Marshallable[ContainedTelepadDeployableData] {
implicit val codec : Codec[ContainedTelepadDeployableData] = (
("unk" | uint(7)) ::
("router_guid" | PlanetSideGUID.codec) ::
uint16 ::
uint4 ::
uint16
).exmap[ContainedTelepadDeployableData] (
{
case unk :: rguid :: 0 :: 8 :: 0 :: HNil =>
Attempt.successful(ContainedTelepadDeployableData(unk, rguid))
case _ :: _ :: _ :: _ :: _ :: HNil =>
Attempt.failure(Err("invalid rek data format"))
},
{
case ContainedTelepadDeployableData(unk, rguid) =>
Attempt.successful(unk :: rguid :: 0 :: 8 :: 0 :: HNil)
}
)
}

View file

@ -4,6 +4,25 @@ package net.psforever.packet.game.objectcreate
import scodec.codecs._
import scodec.Codec
/**
* Values for the different specific customizations available as cosmetics.<br>
* `NoHelmet` removes the current helmet on the reinforced exo-suit and the agile exo-suit;
* other cosmetics require `no_helmet` to be `true` before they can be seen;
* `NoHelmet` does not override `Beret` or `BrimmedCap`<br>
* `Beret` player dons a beret<br>
* `Sunglasses` player dons sunglasses<br>
* `Earpiece` player dons an earpiece on the left<br>
* `BrimmedCap` player dons a cap;
* the cap overrides the beret, if both are selected
*/
object PersonalStyle extends Enumeration {
val BrimmedCap = Value(1)
val Earpiece = Value(2)
val Sunglasses = Value(4)
val Beret = Value(8)
val NoHelmet = Value(16)
}
/**
* The different cosmetics that a player can apply to their character model's head.<br>
* <br>
@ -11,31 +30,99 @@ import scodec.Codec
* These flags are only valid if the player has:
* for `DetailedCharacterData`, achieved at least battle rank twenty-four (battle experience points greater than 2286230),
* or, for `CharacterData`, achieved at least battle rank twenty-five (acquired their third uniform upgrade).
* `CharacterData`, as implied, will not display these options until one battle rank after they would have become available.
* @param no_helmet removes the current helmet on the reinforced exo-suit and the agile exo-suit;
* all other cosmetics require `no_helmet` to be `true` before they can be seen
* @param beret player dons a beret
* @param sunglasses player dons sunglasses
* @param earpiece player dons an earpiece on the left
* @param brimmed_cap player dons a cap;
* the cap overrides the beret, if both are selected
* @see `UniformStyle.ThirdUpgrade`
* `CharacterData`, as suggested, will not display these options until one battle rank after they would have become available.
* @param pstyles a value that indicates certain cosmetic features by bitwise math
* @see `UniformStyle`
* @see `PersonalStyleFeatures`
*/
final case class Cosmetics(no_helmet : Boolean,
beret : Boolean,
sunglasses : Boolean,
earpiece : Boolean,
brimmed_cap : Boolean
) extends StreamBitSize {
final case class Cosmetics(pstyles : Int) extends StreamBitSize {
override def bitsize : Long = 5L
/**
* Transform the accumulated bitwise cosmetic feature integer into a group of all valid cosmetic feature values.
* @return a group of all valid cosmetic feature values
*/
def Styles : Set[PersonalStyle.Value] = {
(for {
style <- PersonalStyle.values.toList
if (pstyles & style.id) == style.id
} yield style) toSet
}
/**
* Allocate a cosmetic feature to an existing group of cosmetic feature values if that feature is not already a member.<br>
* `Cosmetics` is an immutable object so a new object with the additional value must be created.
* @param pstyle the cosmetic feature value
* @return a new `Cosmetics` object, potentially including the new cosmetic feature
*/
def +(pstyle : PersonalStyle.Value) : Cosmetics = {
Cosmetics(pstyles | pstyle.id)
}
/**
* Revoke a cosmetic feature from an existing group of cosmetic feature values if that feature is a member.<br>
* * `Cosmetics` is an immutable object so a new object with the value removed must be created.
* @param pstyle the cosmetic feature value
* @return a new `Cosmetics` object, excluding the new cosmetic feature
*/
def -(pstyle : PersonalStyle.Value) : Cosmetics = {
Cosmetics(pstyles - (pstyles & pstyle.id))
}
/**
* Determine if this `Cosmetics` object contain the given cosmetic feature.
* @param pstyle the cosmetic feature value
* @return `true`, if the feature is included; `false`, otherwise
*/
def contains(pstyle : PersonalStyle.Value) : Boolean = (pstyles & pstyle.id) == pstyle.id
}
object Cosmetics {
implicit val codec : Codec[Cosmetics] = (
("no_helmet" | bool) ::
("beret" | bool) ::
("sunglasses" | bool) ::
("earpiece" | bool) ::
("brimmed_cap" | bool)
).as[Cosmetics]
/**
* Overloaded constructor for `Cosmetics` that loads no option.
* @return a `Cosmetics` object
*/
def apply() : Cosmetics = Cosmetics(0)
/**
* Overloaded constructor for `Cosmetics` that loads a single option.
* @param pstyle the cosmetic feature that will be valid
* @return a `Cosmetics` object
*/
def apply(pstyle : PersonalStyle.Value) : Cosmetics = Cosmetics(pstyle.id)
/**
* Overloaded constructor for `Cosmetics` that loads all options listed.
* @param pstyle all of the cosmetic feature that will be valid
* @return a `Cosmetics` object
*/
def apply(pstyle : Set[PersonalStyle.Value]) : Cosmetics = {
Cosmetics(pstyle.foldLeft(0)(_ + _.id))
}
/**
* Overloaded constructor for `Cosmetics` that list all options as boolean values
* @param no_helmet removes the current helmet on the reinforced exo-suit and the agile exo-suit
* @param beret player dons a beret
* @param sunglasses player dons sunglasses
* @param earpiece player dons an earpiece on the left
* @param brimmed_cap player dons a cap
* @return a `Cosmetics` object
*/
def apply(no_helmet : Boolean,
beret : Boolean,
sunglasses : Boolean,
earpiece : Boolean,
brimmed_cap : Boolean) : Cosmetics = {
implicit def bool2int(b : Boolean) : Int = if(b) 1 else 0
Cosmetics(
(no_helmet * 16) +
(beret * 8) +
(sunglasses * 4) +
(earpiece * 2) +
brimmed_cap
)
}
implicit val codec : Codec[Cosmetics] = uint(5).hlist.as[Cosmetics]
}

View file

@ -1,39 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
/**
* A representation of an adaptive construction engine (ACE).
* This one-time-use item deploys a variety of utilities into the game environment.
* Has an advanced version internally called an `advanced_ace` and commonly called a Field Deployment Unit (FDU).
* @param unk na
*/
final case class DetailedACEData(unk : Int) extends ConstructorData {
override def bitsize : Long = 51L
}
object DetailedACEData extends Marshallable[DetailedACEData] {
implicit val codec : Codec[DetailedACEData] = (
("unk" | uint4L) ::
uint4L ::
uintL(20) ::
uint4L ::
uint16L ::
uint(3)
).exmap[DetailedACEData] (
{
case code :: 8 :: 0 :: 2 :: 0 :: 4 :: HNil =>
Attempt.successful(DetailedACEData(code))
case _ =>
Attempt.failure(Err("invalid ace data format"))
},
{
case DetailedACEData(code) =>
Attempt.successful(code :: 8 :: 0 :: 2 :: 0 :: 4 :: HNil)
}
)
}

View file

@ -3,6 +3,7 @@ package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.PlanetSideEmpire
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
@ -15,14 +16,17 @@ import shapeless.{::, HNil}
* The maximum amount of ammunition that can be stored in a single box is 65535 units.
* Regardless of the interface, however, the number will never be fully visible.
* Only the first three digits or the first four digits may be represented.
* @param unk na
* @param data na
* @param magazine the number of rounds available
* @see DetailedWeaponData
* @see `DetailedWeaponData`
*/
final case class DetailedAmmoBoxData(unk : Int,
final case class DetailedAmmoBoxData(data : CommonFieldData,
magazine : Int
) extends ConstructorData {
override def bitsize : Long = 40L
override def bitsize : Long = {
val dataSize = data.bitsize
17L + dataSize
}
}
object DetailedAmmoBoxData extends Marshallable[DetailedAmmoBoxData] {
@ -37,22 +41,27 @@ object DetailedAmmoBoxData extends Marshallable[DetailedAmmoBoxData] {
def apply(cls : Int, guid : PlanetSideGUID, parentSlot : Int, ammo : DetailedAmmoBoxData) : InternalSlot =
new InternalSlot(cls, guid, parentSlot, ammo)
def apply(unk : Int, mag : Int) : DetailedAmmoBoxData = {
DetailedAmmoBoxData(
CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, unk > 0, None, false, None, None, PlanetSideGUID(0)), mag
)
}
implicit val codec : Codec[DetailedAmmoBoxData] = (
uint4L ::
("unk" | uint4L) :: // 8 - common - 4 - safe, 2 - stream misalignment, 1 - safe, 0 - common
uint(15) ::
("data" | CommonFieldData.codec) ::
("magazine" | uint16L) ::
bool
).exmap[DetailedAmmoBoxData] (
{
case 0xC :: unk :: 0 :: mag :: false :: HNil =>
Attempt.successful(DetailedAmmoBoxData(unk, mag))
case _ =>
Attempt.failure(Err("invalid ammunition data format"))
case data :: mag :: false :: HNil =>
Attempt.successful(DetailedAmmoBoxData(data, mag))
case data =>
Attempt.failure(Err(s"invalid detailed ammunition data format - $data"))
},
{
case DetailedAmmoBoxData(unk, mag) =>
Attempt.successful(0xC :: unk :: 0 :: mag :: false:: HNil)
case DetailedAmmoBoxData(data, mag) =>
Attempt.successful(data :: mag :: false:: HNil)
}
)
}

View file

@ -1,36 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
/**
* A representation of the detonater utility that is created when putting down a Boomer with an ACE.
*/
final case class DetailedBoomerTriggerData() extends ConstructorData {
override def bitsize : Long = 51L
}
object DetailedBoomerTriggerData extends Marshallable[DetailedBoomerTriggerData] {
implicit val codec : Codec[DetailedBoomerTriggerData] = (
uint8L ::
uint(22) ::
bool :: //true
uint(17) ::
bool :: //true
uint2L
).exmap[DetailedBoomerTriggerData] (
{
case 0xC8 :: 0 :: true :: 0 :: true :: 0 :: HNil =>
Attempt.successful(DetailedBoomerTriggerData())
case _ :: _ :: _ :: _ :: _ :: _ :: HNil =>
Attempt.failure(Err("invalid command detonater format"))
},
{
case DetailedBoomerTriggerData() =>
Attempt.successful(0xC8 :: 0 :: true :: 0 :: true :: 0 :: HNil)
}
)
}

View file

@ -7,32 +7,32 @@ import scodec.codecs._
import shapeless.{::, HNil}
/**
* A representation of the command uplink device.<br>
* I don't know much about the command uplink device so someone else has to provide this commentary.
* A representation of the command uplink device.
*/
final case class DetailedCommandDetonaterData(unk1 : Int = 8,
unk2 : Int = 0) extends ConstructorData {
override def bitsize : Long = 51L
final case class DetailedCommandDetonaterData(data : CommonFieldData) extends ConstructorData {
override def bitsize : Long = {
val dataSize = data.bitsize
28L + dataSize
}
}
object DetailedCommandDetonaterData extends Marshallable[DetailedCommandDetonaterData] {
implicit val codec : Codec[DetailedCommandDetonaterData] = (
("unk1" | uint4L) ::
("unk2" | uint4L) ::
uint(20) ::
uint4L ::
("data" | CommonFieldData.codec) ::
uint8 ::
uint16 ::
uint(3)
uint4
).exmap[DetailedCommandDetonaterData] (
{
case unk1 :: unk2 :: 0 :: 2 :: 0 :: 4 :: HNil =>
Attempt.successful(DetailedCommandDetonaterData(unk1, unk2))
case _ =>
Attempt.failure(Err("invalid command detonator data format"))
case data :: 1 :: 0 :: 4 :: HNil =>
Attempt.successful(DetailedCommandDetonaterData(data))
case data =>
Attempt.failure(Err(s"invalid detailed command detonater data format - $data"))
},
{
case DetailedCommandDetonaterData(unk1, unk2) =>
Attempt.successful(unk1 :: unk2 :: 0 :: 2 :: 0 :: 4 :: HNil)
case DetailedCommandDetonaterData(data) =>
Attempt.successful(data :: 1 :: 0 :: 4 :: HNil)
}
)
}

View file

@ -0,0 +1,37 @@
// Copyright (c) 2018 PSForever
package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
import scodec.codecs._
import scodec.{Attempt, Codec, Err}
import shapeless.{::, HNil}
/**
* `DetailedACEData` - `data.faction` is faction affinity, `data.unk1` is `true`
* `DetailedBoomerTriggerData` - `data.faction` can be `NEUTRAL`, `data.unk1` is `true`
* `DetailedTelepadData` - `data.faction` can be `NEUTRAL`, `data.jammered` is the router's GUID
*/
final case class DetailedConstructionToolData(data : CommonFieldData) extends ConstructorData {
override def bitsize : Long = 28L + data.bitsize
}
object DetailedConstructionToolData extends Marshallable[DetailedConstructionToolData] {
implicit val codec : Codec[DetailedConstructionToolData] = (
("data" | CommonFieldData.codec(false)) ::
uint8 ::
uint(18) ::
uint2
).exmap[DetailedConstructionToolData] (
{
case data :: 1 :: 1 :: _ :: HNil =>
Attempt.successful(DetailedConstructionToolData(data))
case data =>
Attempt.failure(Err(s"invalid detailed construction tool data format - $data"))
},
{
case DetailedConstructionToolData(data) =>
Attempt.successful(data :: 1 :: 1 :: 0 :: HNil)
}
)
}

View file

@ -3,6 +3,7 @@ package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.PlanetSideEmpire
import scodec.codecs._
import scodec.{Attempt, Codec, Err}
import shapeless.{::, HNil}
@ -14,10 +15,10 @@ import shapeless.{::, HNil}
* the actual container for them, in grid format, can only be accessed by interacting with locker objects in the game world.
* Items are generally added and removed in the same way as with any other opened inventory.
* Unlike other inventories, however, locker space is personal to an avatar and can not be accessed by other players.
* @param unk na
* @param data na
* @param inventory the items in this inventory
*/
final case class DetailedLockerContainerData(unk : Int,
final case class DetailedLockerContainerData(data : CommonFieldData,
inventory : Option[InventoryData]
) extends ConstructorData {
override def bitsize : Long = {
@ -34,7 +35,7 @@ object DetailedLockerContainerData extends Marshallable[DetailedLockerContainerD
* @return a `DetailedLockerContainerData` object
*/
def apply(unk : Int) : DetailedLockerContainerData =
new DetailedLockerContainerData(unk, None)
new DetailedLockerContainerData(CommonFieldData(PlanetSideEmpire.NEUTRAL, unk), None)
/**
* Overloaded constructor for creating `DetailedLockerContainerData` containing known items.
@ -43,7 +44,7 @@ object DetailedLockerContainerData extends Marshallable[DetailedLockerContainerD
* @return a `DetailedLockerContainerData` object
*/
def apply(unk : Int, inventory : List[InternalSlot]) : DetailedLockerContainerData =
new DetailedLockerContainerData(unk, Some(InventoryData(inventory)))
new DetailedLockerContainerData(CommonFieldData(PlanetSideEmpire.NEUTRAL, unk), Some(InventoryData(inventory)))
/**
* Overloaded constructor for creating `DetailedLockerContainerData` while masking use of `InternalSlot`.
@ -57,27 +58,26 @@ object DetailedLockerContainerData extends Marshallable[DetailedLockerContainerD
new InternalSlot(cls, guid, parentSlot, locker)
implicit val codec : Codec[DetailedLockerContainerData] = (
uint4L ::
("unk" | uint4L) :: // 8 - common - 4 - safe, 2 - stream misalignment, 1 - safe, 0 - common
uint(15) ::
("data" | CommonFieldData.codec) ::
uint16L :: //always 1
optional(bool, InventoryData.codec_detailed)
).exmap[DetailedLockerContainerData] (
{
case 0xC :: unk :: 0 :: 1 :: None :: HNil =>
Attempt.successful(DetailedLockerContainerData(unk, None))
case data :: 1 :: None :: HNil =>
Attempt.successful(DetailedLockerContainerData(data, None))
case 0xC :: unk :: 0 :: 1 :: Some(inv) :: HNil =>
Attempt.successful(DetailedLockerContainerData(unk, Some(inv)))
case _ =>
Attempt.failure(Err(s"invalid locker container data format"))
case data :: 1 :: Some(inv) :: HNil =>
Attempt.successful(DetailedLockerContainerData(data, Some(inv)))
case data =>
Attempt.failure(Err(s"invalid detailed locker container data format - $data"))
},
{
case DetailedLockerContainerData(unk, None) =>
Attempt.successful(0xC :: unk :: 0 :: 1 :: None :: HNil)
case DetailedLockerContainerData(data, None) =>
Attempt.successful(data :: 1 :: None :: HNil)
case DetailedLockerContainerData(unk, Some(inv)) =>
Attempt.successful(0xC :: unk :: 0 :: 1 :: Some(inv) :: HNil)
case DetailedLockerContainerData(data, Some(inv)) =>
Attempt.successful(data :: 1 :: Some(inv) :: HNil)
}
)
}

View file

@ -60,7 +60,7 @@ object DetailedPlayerData extends Marshallable[DetailedPlayerData] {
* technically, always `DrawnSlot.None`, but the field is preserved to maintain similarity
* @return a `DetailedPlayerData` object
*/
def apply(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Option[Int])=>DetailedCharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Value) : DetailedPlayerData = {
def apply(basic_appearance : Int=>CharacterAppearanceData, character_data : Option[Int]=>DetailedCharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Value) : DetailedPlayerData = {
val appearance = basic_appearance(5)
DetailedPlayerData(None, appearance, character_data(appearance.altModelBit), Some(inventory), drawn_slot)(false)
}
@ -75,7 +75,7 @@ object DetailedPlayerData extends Marshallable[DetailedPlayerData] {
* technically, always `DrawnSlot.None`, but the field is preserved to maintain similarity
* @return a `DetailedPlayerData` object
*/
def apply(basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Option[Int])=>DetailedCharacterData, drawn_slot : DrawnSlot.Value) : DetailedPlayerData = {
def apply(basic_appearance : Int=>CharacterAppearanceData, character_data : Option[Int]=>DetailedCharacterData, drawn_slot : DrawnSlot.Value) : DetailedPlayerData = {
val appearance = basic_appearance(5)
DetailedPlayerData(None, appearance, character_data(appearance.altModelBit), None, drawn_slot)(false)
}
@ -91,7 +91,7 @@ object DetailedPlayerData extends Marshallable[DetailedPlayerData] {
* @param drawn_slot the holster that is depicted as exposed, or "drawn"
* @return a `DetailedPlayerData` object
*/
def apply(pos : PlacementData, basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Option[Int])=>DetailedCharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Value) : DetailedPlayerData = {
def apply(pos : PlacementData, basic_appearance : Int=>CharacterAppearanceData, character_data : Option[Int]=>DetailedCharacterData, inventory : InventoryData, drawn_slot : DrawnSlot.Value) : DetailedPlayerData = {
val appearance = basic_appearance(PlayerData.PaddingOffset(Some(pos)))
DetailedPlayerData(Some(pos), appearance, character_data(appearance.altModelBit), Some(inventory), drawn_slot)(true)
}
@ -106,7 +106,7 @@ object DetailedPlayerData extends Marshallable[DetailedPlayerData] {
* @param drawn_slot the holster that is depicted as exposed, or "drawn"
* @return a `DetailedPlayerData` object
*/
def apply(pos : PlacementData, basic_appearance : (Int)=>CharacterAppearanceData, character_data : (Option[Int])=>DetailedCharacterData, drawn_slot : DrawnSlot.Value) : DetailedPlayerData = {
def apply(pos : PlacementData, basic_appearance : Int=>CharacterAppearanceData, character_data : Option[Int]=>DetailedCharacterData, drawn_slot : DrawnSlot.Value) : DetailedPlayerData = {
val appearance = basic_appearance(PlayerData.PaddingOffset(Some(pos)))
DetailedPlayerData(Some(pos), appearance, character_data(appearance.altModelBit), None, drawn_slot)(true)
}

View file

@ -11,34 +11,36 @@ import shapeless.{::, HNil}
* This data will help construct the "tool" called a Remote Electronics Kit.<br>
* <br>
* Of note is the first portion of the data which resembles the `DetailedWeaponData` format.
* @param unk1 na
* @param unk2 na
* @param data na
* @param unk na
*/
final case class DetailedREKData(unk1 : Int,
unk2 : Int = 0
final case class DetailedREKData(data : CommonFieldData,
unk : Int = 0
) extends ConstructorData {
override def bitsize : Long = 67L
override def bitsize : Long = {
val dataSize = data.bitsize
43L + dataSize
}
}
object DetailedREKData extends Marshallable[DetailedREKData] {
implicit val codec : Codec[DetailedREKData] = (
("unk" | uint4L) ::
uint4L ::
uintL(20) ::
uint4L ::
("data" | CommonFieldData.codec2) ::
uint8 ::
uint16L ::
uint4L ::
("unk2" | uintL(15))
("unk" | uint8) ::
uint(7)
).exmap[DetailedREKData] (
{
case code :: 8 :: 0 :: 2 :: 0 :: 8 :: unk2 :: HNil =>
Attempt.successful(DetailedREKData(code, unk2))
case _ =>
Attempt.failure(Err("invalid rek data format"))
case data :: 2 :: 0 :: 8 :: unk :: 0 :: HNil =>
Attempt.successful(DetailedREKData(data, unk))
case data =>
Attempt.failure(Err(s"invalid detailed rek data format - $data"))
},
{
case DetailedREKData(code, unk2) =>
Attempt.successful(code :: 8 :: 0 :: 2 :: 0 :: 8 :: unk2 :: HNil)
case DetailedREKData(data, unk) =>
Attempt.successful(data :: 2 :: 0 :: 8 :: unk :: 0 :: HNil)
}
)
}

View file

@ -1,48 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
import net.psforever.packet.game.PlanetSideGUID
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
/**
* A representation of the telepad portion of `ObjectCreateDetailedMessage` packet data.
* This data will help construct the "cosntruction tool"
* that can be obtained from the Router vehicle - the Router telepad.
* It issued to construct a bidirectional teleportation point associated with a Router if that Router is deployed.
* @param unk na
* @param router_guid the Router
*/
final case class DetailedTelepadData(unk : Int, router_guid : Option[PlanetSideGUID]) extends ConstructorData {
override def bitsize : Long = {
val rguidSize = if(router_guid.nonEmpty) 16 else 0
51L + rguidSize
}
}
object DetailedTelepadData extends Marshallable[DetailedTelepadData] {
def apply(unk : Int) : DetailedTelepadData = DetailedTelepadData(unk, None)
def apply(unk : Int, router_guid : PlanetSideGUID) : DetailedTelepadData = DetailedTelepadData(unk, Some(router_guid))
implicit val codec : Codec[DetailedTelepadData] = (
("unk" | uint(6)) ::
optional(bool, "router_guid" | PlanetSideGUID.codec) ::
uint(24) ::
uint(18) ::
uint2
).exmap[DetailedTelepadData] (
{
case unk :: rguid :: 1 :: 1 :: 0 :: HNil =>
Attempt.successful(DetailedTelepadData(unk, rguid))
case _ =>
Attempt.failure(Err("invalid detailed telepad format"))
},
{
case DetailedTelepadData(unk, rguid) =>
Attempt.successful(unk :: rguid :: 1 :: 1 :: 0 :: HNil)
}
)
}

View file

@ -3,6 +3,7 @@ package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.PlanetSideEmpire
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
@ -14,29 +15,21 @@ import shapeless.{::, HNil}
* The data for the weapons nests information for the default (current) type and number of ammunition in its magazine.
* This ammunition data essentially is the weapon's magazines as numbered slots.
* An "expected" number of ammunition slot data can be passed into the function.
* @param unk1 na
* @param unk2 na
* @param data field common to multiple game objects
* @param fire_mode the current fire mode
* @param ammo data regarding the currently loaded ammunition type(s) and quantity(ies)
* @param mag_capacity implicit;
* the total number of concurrently-loaded ammunition types allowed in this weapon;
* concurrent ammunition does not need to be unloaded to be switched;
* defaults to 1;
* 0 is invalid;
* -1 or less ignores the imposed checks
* @see `DetailedAmmoBoxData`
* @see `WeaponData`
*/
final case class DetailedWeaponData(unk1 : Int,
unk2 : Int,
final case class DetailedWeaponData(data : CommonFieldData,
fire_mode : Int,
ammo : List[InternalSlot]
)(implicit val mag_capacity : Int = 1) extends ConstructorData {
ammo : List[InternalSlot],
unk : Boolean = false
) extends ConstructorData {
override def bitsize : Long = {
var bitsize : Long = 0L
for(o <- ammo) {
bitsize += o.bitsize
}
61L + bitsize //51 + 10 (from InventoryData) + ammo
val dataSize = data.bitsize
val ammoSize : Long = ammo.foldLeft(0L)(_ + _.bitsize)
38L + dataSize + ammoSize //28 + 10 (from InventoryData) + ammo
}
}
@ -51,8 +44,23 @@ object DetailedWeaponData extends Marshallable[DetailedWeaponData] {
* @param ammo the constructor data for the ammunition
* @return a `DetailedWeaponData` object
*/
def apply(unk1 : Int, unk2 : Int, cls : Int, guid : PlanetSideGUID, parentSlot : Int, ammo : DetailedAmmoBoxData) : DetailedWeaponData =
new DetailedWeaponData(unk1, unk2, 0, InternalSlot(cls, guid, parentSlot, ammo) :: Nil)
def apply(unk1 : Int, unk2 : Int, cls : Int, guid : PlanetSideGUID, parentSlot : Int, ammo : DetailedAmmoBoxData) : DetailedWeaponData = {
DetailedWeaponData(
CommonFieldData(
PlanetSideEmpire(unk1 & 3),
false,
false,
(unk2 & 8) == 8,
None,
(unk2 & 4) == 4,
None,
None,
PlanetSideGUID(0)
),
0,
List(InternalSlot(cls, guid, parentSlot, ammo))
)
}
/**
@ -65,71 +73,58 @@ object DetailedWeaponData extends Marshallable[DetailedWeaponData] {
* @param ammo the constructor data for the ammunition
* @return a `DetailedWeaponData` object
*/
def apply(unk1 : Int, unk2 : Int, fire_mode : Int, cls : Int, guid : PlanetSideGUID, parentSlot : Int, ammo : DetailedAmmoBoxData) : DetailedWeaponData =
new DetailedWeaponData(unk1, unk2, fire_mode, InternalSlot(cls, guid, parentSlot, ammo) :: Nil)
def apply(unk1 : Int, unk2 : Int, fire_mode : Int, cls : Int, guid : PlanetSideGUID, parentSlot : Int, ammo : DetailedAmmoBoxData) : DetailedWeaponData = {
DetailedWeaponData(
CommonFieldData(
PlanetSideEmpire(unk1 & 3),
false,
false,
(unk2 & 8) == 8,
None,
(unk2 & 4) == 4,
None,
None,
PlanetSideGUID(0)
),
fire_mode,
List(InternalSlot(cls, guid, parentSlot, ammo))
)
}
/**
* A `Codec` for `DetailedWeaponData`
* @param mag_capacity the total number of concurrently-loaded ammunition types allowed in this weapon;
* defaults to 1
* @return a `WeaponData` object or a `BitVector`
*/
def codec(mag_capacity : Int = 1) : Codec[DetailedWeaponData] = (
("unk1" | uint(3)) ::
bool :: //weapon refuses to shoot if set (not weapons lock?)
("unk2" | uint4L) :: //8 - common; 4 - jammers weapons; 2 - weapon breaks; 1, 0 - safe
uint24 ::
uint(12) ::
("fire_mode" | uint(3)) :: //TODO size?
uint(3) ::
("ammo" | InventoryData.codec_detailed) ::
bool
implicit val codec : Codec[DetailedWeaponData] = (
("data" | CommonFieldData.codec) ::
uint8 ::
uint8 ::
("fire_mode" | uint8) ::
uint2 ::
optional(bool, "ammo" | InventoryData.codec_detailed) ::
("unk" | bool)
).exmap[DetailedWeaponData] (
{
case unk1 :: false :: unk2 :: 2 :: 0 :: fmode :: 3 :: InventoryData(ammo) :: false :: HNil =>
case data :: 1 :: 0 :: fmode :: 1 :: Some(InventoryData(ammo)) :: unk :: HNil =>
val magSize = ammo.size
if(mag_capacity == 0 || magSize == 0) {
if(magSize == 0) {
Attempt.failure(Err("weapon must decode some ammunition"))
}
else if(mag_capacity > 0 && magSize != mag_capacity) {
Attempt.failure(Err(s"weapon decodes too much or too little ammunition - actual $magSize, expected $mag_capacity"))
}
else {
Attempt.successful(DetailedWeaponData(unk1, unk2, fmode, ammo)(magSize))
Attempt.successful(DetailedWeaponData(data, fmode, ammo, unk))
}
case _ =>
Attempt.failure(Err("invalid weapon data format"))
case data =>
Attempt.failure(Err(s"invalid weapon data format - $data"))
},
{
case obj @ DetailedWeaponData(unk1, unk2, fmode, ammo) =>
case DetailedWeaponData(data, fmode, ammo, unk) =>
val magSize = ammo.size
val magCapacity = obj.mag_capacity
if(mag_capacity == 0 || magCapacity == 0 || magSize == 0) {
if(magSize == 0) {
Attempt.failure(Err("weapon must encode some ammunition"))
}
else if(magCapacity < 0 || mag_capacity < 0) {
Attempt.successful(unk1 :: false :: unk2 :: 2 :: 0 :: fmode :: 3 :: InventoryData(ammo) :: false :: HNil)
else if(magSize >= 255) {
Attempt.failure(Err("weapon encodes too much ammunition (255+ types!)"))
}
else {
if(magCapacity != mag_capacity) {
Attempt.failure(Err(s"different encoding expectations for amount of ammunition - actual $magCapacity, expected $mag_capacity"))
}
else if(magSize != mag_capacity) {
Attempt.failure(Err(s"weapon encodes wrong amount of ammunition - actual $magSize, expected $mag_capacity"))
}
else if(magSize >= 255) {
Attempt.failure(Err("weapon encodes too much ammunition (255+ types!)"))
}
else {
Attempt.successful(unk1 :: false :: unk2 :: 2 :: 0 :: fmode :: 3 :: InventoryData(ammo) :: false :: HNil)
}
Attempt.successful(data :: 1 :: 0 :: fmode :: 1 :: Some(InventoryData(ammo)) :: unk :: HNil)
}
case _ =>
Attempt.failure(Err("invalid weapon data format"))
}
)
implicit val codec : Codec[DetailedWeaponData] = codec()
}

View file

@ -17,7 +17,7 @@ final case class DroppedItemData[T <: ConstructorData](pos : PlacementData, obj
object DroppedItemData {
/**
* Transform `DroppedItemData[T]` for object type `T` into `ConstructorData.genericPattern`.<br>
* Transform `DroppedItemData[T]` for object type `T` into `ConstructorData`.<br>
* <br>
* This function eliminates the need to have a separate "DroppedFooData" class for every object "Foo."
* Two functions normally perform this transformation: an `implicit` `codec` used in a `genericCodec`.
@ -32,10 +32,10 @@ object DroppedItemData {
* @param objType a `String` that explains what the object should be identified as in the log;
* defaults to "object"
* @tparam T a subclass of `ConstructorData` that indicates what type the object is
* @return `ConstructorData.genericPattern`
* @see `ConstructorData.genericPattern` (function)
* @return `Codec[ConstructorData]`
* @see `ConstructorData` (function)
*/
def genericCodec[T <: ConstructorData](objCodec : Codec[T], objType : String = "object") : Codec[ConstructorData.genericPattern] = (
def apply[T <: ConstructorData](objCodec : Codec[T], objType : String = "object") : Codec[ConstructorData] = (
("pos" | PlacementData.codec) ::
("obj" | objCodec)
).xmap[DroppedItemData[T]] (
@ -47,16 +47,24 @@ object DroppedItemData {
case DroppedItemData(pos, obj) =>
pos :: obj :: HNil
}
).exmap[ConstructorData.genericPattern] (
{
case x =>
Attempt.successful(Some(x.asInstanceOf[ConstructorData]))
).exmap[ConstructorData] (
x => {
try {
Attempt.successful(x.asInstanceOf[ConstructorData])
}
catch {
case ex : Exception =>
Attempt.failure(Err(s"can not cast decode of $x to dropped $objType - $ex"))
}
},
{
case Some(x) =>
Attempt.successful(x.asInstanceOf[DroppedItemData[T]])
case _ =>
Attempt.failure(Err(s"can not encode dropped $objType data"))
x => {
try {
Attempt.successful(x.asInstanceOf[DroppedItemData[T]]) //why does this work? shouldn't type erasure be a problem?
}
catch {
case ex : Exception =>
Attempt.failure(Err(s"can not cast encode $x to dropped $objType - $ex"))
}
}
)
}

View file

@ -3,7 +3,7 @@ package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
import scodec.codecs._
import scodec.{Attempt, Codec}
import scodec.{Attempt, Codec, Err}
import shapeless.{::, HNil}
/**
@ -31,7 +31,7 @@ import shapeless.{::, HNil}
* @see `DroppodLaunchRequestMessage`
* @see `DroppodLaunchResponseMessage`
*/
final case class DroppodData(basic : CommonFieldData,
final case class DroppodData(basic : CommonFieldDataWithPlacement,
burn : Boolean = false,
health : Int = 255
) extends ConstructorData {
@ -43,7 +43,7 @@ final case class DroppodData(basic : CommonFieldData,
object DroppodData extends Marshallable[DroppodData] {
implicit val codec : Codec[DroppodData] = (
("basic" | CommonFieldData.codec) ::
("basic" | CommonFieldDataWithPlacement.codec) ::
bool ::
("health" | uint8L) :: //health
uintL(5) :: //0x0
@ -56,6 +56,9 @@ object DroppodData extends Marshallable[DroppodData] {
case basic :: false :: health :: 0 :: 0xF :: 0 :: boosters :: false :: HNil =>
val burn : Boolean = boosters == 0
Attempt.successful(DroppodData(basic, burn, health))
case data =>
Attempt.failure(Err(s"invalid droppod data format - $data"))
},
{
case DroppodData(basic, burn, health) =>

View file

@ -0,0 +1,46 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
/**
* A representation of a number of simplified objects that the user can hold in their hands, including:
* the advanced construction engine (`ace`),
* the field deployable unit (`advanced_ace`),
* the boomer trigger apparatus,
* the remote telepad (not deployed),
* the flail laser pointer (`flail_targeting_laser`),
* and the command uplink device (`command_detonater`).
* @param data fields that are common to this game object
* - v4 - not used, i.e., the simple format `CommonFieldData` object is employed
* - v5 - for the telepad, this field is expected to be the GUID of the associated Router
*/
final case class HandheldData(data : CommonFieldData) extends ConstructorData {
override def bitsize : Long = {
11L + data.bitsize
}
}
object HandheldData extends Marshallable[HandheldData] {
implicit val codec : Codec[HandheldData] = (
("data" | CommonFieldData.codec) ::
uint4 ::
uint4 ::
uint(3)
).exmap[HandheldData] (
{
case data :: 0 :: 0 :: 0 :: HNil =>
Attempt.successful(HandheldData(data))
case data =>
Attempt.failure(Err(s"invalid handheld tool data format - $data"))
},
{
case HandheldData(data) =>
Attempt.successful(data :: 0 :: 0 :: 0 :: HNil)
}
)
}

View file

@ -45,12 +45,12 @@ object InternalSlot {
}
).xmap[InternalSlot] (
{
case cls :: guid :: slot :: Some(obj) :: HNil =>
case cls :: guid :: slot :: obj :: HNil =>
InternalSlot(cls, guid, slot, obj)
},
{
case InternalSlot(cls, guid, slot, obj) =>
cls :: guid :: slot :: Some(obj) :: HNil
cls :: guid :: slot :: obj :: HNil
}
)
@ -65,12 +65,12 @@ object InternalSlot {
}
).xmap[InternalSlot] (
{
case cls :: guid :: slot :: Some(obj) :: HNil =>
case cls :: guid :: slot :: obj :: HNil =>
InternalSlot(cls, guid, slot, obj)
},
{
case InternalSlot(cls, guid, slot, obj) =>
cls :: guid :: slot :: Some(obj) :: HNil
cls :: guid :: slot :: obj :: HNil
}
)
}

View file

@ -7,19 +7,9 @@ import scodec.{Attempt, Codec, Err}
import shapeless.{::, HNil}
/**
* A representation of the Spitfire-based small turrets deployed using an adaptive construction engine.<br>
* <br>
* The turret may contain substructure defining a weapon is a turret weapon contained within the turret itself.
* Furthermore, that turret-like weapon is loaded with turret-like ammunition.
* In other words, this outer turret can be considered a weapons platform for the inner turret weapon.<br>
* <br>
* If the turret has no `health`, it is rendered as destroyed.
* If the turret has no internal weapon, it is safest rendered as destroyed.
* @param deploy data common to objects spawned by the (advanced) adaptive construction engine
* @param health the amount of health the object has, as a percentage of a filled bar
* @param internals data regarding the mounted weapon
* This class currently is unused but is based on the `SmallTurretData` `Codec` class.
*/
final case class LargeDeployableData(deploy : SmallDeployableData,
final case class LargeDeployableData(deploy : CommonFieldDataWithPlacement,
health : Int,
internals : Option[InventoryData] = None
) extends ConstructorData {
@ -37,7 +27,7 @@ final case class LargeDeployableData(deploy : SmallDeployableData,
object LargeDeployableData extends Marshallable[LargeDeployableData] {
implicit val codec : Codec[LargeDeployableData] = (
("deploy" | SmallDeployableData.codec) ::
("deploy" | CommonFieldDataWithPlacement.codec2) ::
("health" | uint8L) ::
uintL(7) ::
uint4L ::
@ -54,8 +44,9 @@ object LargeDeployableData extends Marshallable[LargeDeployableData] {
}
Attempt.successful(LargeDeployableData(deploy, newHealth, newInternals))
case _ =>
Attempt.failure(Err("invalid large deployable data format"))
case data =>
Attempt.failure(Err(s"invalid large deployable data format - $data"))
},
{
case LargeDeployableData(deploy, health, internals) =>

View file

@ -12,27 +12,39 @@ import shapeless.{::, HNil}
* For whatever reason, these "lockers" are typically placed at the origin coordinates.
* @param inventory the items inside this locker
*/
final case class LockerContainerData(inventory : InventoryData) extends ConstructorData {
override def bitsize : Long = 105L + inventory.bitsize //81u + 2u + 21u + 1u
final case class LockerContainerData(inventory : Option[InventoryData]) extends ConstructorData {
override def bitsize : Long = {
val base : Long = 105L
(inventory match {
case Some(inv) => inv.bitsize
case None => 0L
}) + base //81u + 2u + 21u + 1u
}
}
object LockerContainerData extends Marshallable[LockerContainerData] {
def apply() : LockerContainerData = new LockerContainerData(None)
def apply(inventory : InventoryData) : LockerContainerData = new LockerContainerData(Some(inventory))
def apply(inventory : List[InternalSlot]) : LockerContainerData = new LockerContainerData(Some(InventoryData(inventory)))
implicit val codec : Codec[LockerContainerData] = (
uint32 :: uint32 :: uint(17) :: //can substitute with PlacementData, if ever necessary
uint32 :: uint32 :: uint(17) ::
uint2L ::
uint(21) ::
bool ::
InventoryData.codec
("inventory" | optional(bool, InventoryData.codec))
).exmap[LockerContainerData] (
{
case 0 :: 0 :: 0 :: 3 :: 0 :: true :: inv :: HNil =>
Attempt.successful(LockerContainerData(inv))
case _ =>
Attempt.failure(Err("invalid locker container format"))
case 0 :: 0 :: 0 :: 3 :: 0 :: inventory :: HNil =>
Attempt.successful(LockerContainerData(inventory))
case data =>
Attempt.failure(Err(s"invalid locker container data format - $data"))
},
{
case LockerContainerData(inv) =>
Attempt.successful(0L :: 0L :: 0 :: 3 :: 0 :: true :: inv :: HNil)
case LockerContainerData(inventory) =>
Attempt.successful(0L :: 0L :: 0 :: 3 :: 0 :: inventory :: HNil)
}
)
}

Some files were not shown because too many files have changed in this diff Show more