Deployables (#230)

* functions for certifcation ui updates (that don't work)

* initialization of combat engineering deployables ui on load and certification change

* representation classes for ACE and FDU; ability to pull ACE and FDU from equipment terminals

* ammo change functionality and fire mode change functionality for ConstructionItems refactored from Tool operations and supported properly (switch between deployable options)

* zone-specific structure for keeping track of deployables; abaility to dismiss deployables from the map screen (previous functionality); local client creation of explosive-type deployables

* refactored MannedTurret into FacilityTurret and lesser traits to be used in the deployable spitfires and the OMFT's; all ACE deployables are available for placement; partial management of the construction items after the deployable is placed; boomers create boomer triggers

* Avatar-specific storage for deployables and for updating UI elements

* refactored quite a bit of code in WSA for the benefit of deployable management; refinements to deployable creation; server messages about deployable quantities; corrected the FDU encoding pattern; lots of work dedicated just to synchronizing BoomerTrigger objects

* added RemoverActor for deployables and redistributed deconstruction functionality away from WSA to new DeployableRemover; added events to facilitate activities not inheritable with this model

* refactored and distributed Deployables classes; copious amounts of testing and document-writing

* boomers now explode from trigger; support for deployables being destroyed by weapon discharge, including individual health, soure identification, and damage model; shuffled deployable classes to build different hierarchy

* sensor_shield was skipped by accident

* identified stray object in Hanish, Ishundar, and added Irkalla, Ishundar's capture console; fixed issue with Warp command and 'Irkalla'; modified building amenity setup and setup testing in Zone; players load and die properly when seated in an omft; reserve ammunition in omft properly registered

* added local service channel, capture consoles, fixed tests as much as posible

* fixed LocalService tests by booting the ServiceManager; added avatar and local tests

* a simple attempt to refactor Actor messages in a way that is acceptable to Travis CI

* making the explosive deployables vanish upon explosion; sensor health bars are now supported
This commit is contained in:
Fate-JH 2018-09-23 08:00:58 -04:00 committed by GitHub
parent 6cd18c5623
commit 5f3e7e5df8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
113 changed files with 8155 additions and 2312 deletions

View file

@ -1,6 +1,7 @@
// Copyright (c) 2017 PSForever
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.loadouts.Loadout
@ -37,6 +38,8 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex :
}
}
private val deployables : DeployableToolbox = new DeployableToolbox
def BEP : Long = bep
def BEP_=(battleExperiencePoints : Long) : Long = {
@ -177,6 +180,8 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex :
}
}
def Deployables : DeployableToolbox = deployables
def Definition : AvatarDefinition = GlobalDefinitions.avatar
/*

View file

@ -0,0 +1,24 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
import net.psforever.objects.definition.DeployableDefinition
class BoomerDeployable(cdef : DeployableDefinition) extends ExplosiveDeployable(cdef) {
private var trigger : Option[BoomerTrigger] = None
def Trigger : Option[BoomerTrigger] = trigger
def Trigger_=(item : BoomerTrigger) : Option[BoomerTrigger] = {
if(trigger.isEmpty) { //can only set trigger once
trigger = Some(item)
}
Trigger
}
def Trigger_=(item : Option[BoomerTrigger]) : Option[BoomerTrigger] = {
if(item.isEmpty) {
trigger = None
}
Trigger
}
}

View file

@ -0,0 +1,6 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
import net.psforever.objects.equipment.RemoteUnit
class BoomerTrigger extends SimpleItem(GlobalDefinitions.boomer_trigger) with RemoteUnit

View file

@ -1,26 +1,61 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
import net.psforever.objects.definition.ConstructionItemDefinition
import net.psforever.objects.equipment.{CItem, Equipment, FireModeSwitch}
import net.psforever.objects.ce.DeployedItem
import net.psforever.objects.definition.{ConstructionFireMode, ConstructionItemDefinition}
import net.psforever.objects.equipment.{Equipment, FireModeSwitch}
import net.psforever.types.CertificationType
class ConstructionItem(private val cItemDef : ConstructionItemDefinition) extends Equipment with FireModeSwitch[CItem.DeployedItem.Value] {
/**
* A type of `Equipment` that can be wielded and applied to the game world to produce other game objects.<br>
* <br>
* Functionally, `ConstructionItem` objects resemble `Tool` objects that have fire mode state and alternate "ammunition."
* Very much unlike `Tool` object counterparts, however,
* the alternate "ammunition" is also a type of fire mode state
* maintained in a two-dimensional grid of related states.
* These states represent output products called deployables or, in the common vernacular, CE.
* Also unlike `Tool` objects, whose ammunition is always available even when drawing the weapon is not permitted,
* the different states are not all available if just the equipment itself is available.
* Parameters along with these CE states
* indicate whether the current output product is something the player is permitted to utilize.
* @param cItemDef the `ObjectDefinition` that constructs this item and maintains some of its immutable fields
*/
class ConstructionItem(private val cItemDef : ConstructionItemDefinition) extends Equipment
with FireModeSwitch[ConstructionFireMode] {
private var fireModeIndex : Int = 0
private var ammoTypeIndex : Int = 0
def FireModeIndex : Int = fireModeIndex
def FireModeIndex_=(index : Int) : Int = {
fireModeIndex = index % cItemDef.Modes.length
fireModeIndex = index % Definition.Modes.length
FireModeIndex
}
def FireMode : CItem.DeployedItem.Value = cItemDef.Modes(fireModeIndex)
def FireMode : ConstructionFireMode = Definition.Modes(fireModeIndex)
def NextFireMode : CItem.DeployedItem.Value = {
def NextFireMode : ConstructionFireMode = {
FireModeIndex = FireModeIndex + 1
ammoTypeIndex = 0
FireMode
}
def AmmoTypeIndex : Int = ammoTypeIndex
def AmmoTypeIndex_=(index : Int) : Int = {
ammoTypeIndex = index % FireMode.Deployables.length
AmmoTypeIndex
}
def AmmoType : DeployedItem.Value = FireMode.Deployables(ammoTypeIndex)
def NextAmmoType : DeployedItem.Value = {
AmmoTypeIndex = AmmoTypeIndex + 1
FireMode.Deployables(ammoTypeIndex)
}
def ModePermissions : Set[CertificationType.Value] = FireMode.Permissions(ammoTypeIndex)
def Definition : ConstructionItemDefinition = cItemDef
}

View file

@ -0,0 +1,28 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
import net.psforever.objects.ce.{Deployable, DeployedItem}
object Deployables {
object Make {
def apply(item : DeployedItem.Value) : ()=>PlanetSideGameObject with Deployable = cemap(item)
private val cemap : Map[DeployedItem.Value, ()=>PlanetSideGameObject with Deployable] = Map(
DeployedItem.boomer -> { ()=> new BoomerDeployable(GlobalDefinitions.boomer) },
DeployedItem.he_mine -> { ()=> new ExplosiveDeployable(GlobalDefinitions.he_mine) },
DeployedItem.jammer_mine -> { ()=> new ExplosiveDeployable(GlobalDefinitions.jammer_mine) },
DeployedItem.spitfire_turret -> { ()=> new TurretDeployable(GlobalDefinitions.spitfire_turret) },
DeployedItem.spitfire_cloaked -> { ()=> new TurretDeployable(GlobalDefinitions.spitfire_cloaked) },
DeployedItem.spitfire_aa -> { ()=> new TurretDeployable(GlobalDefinitions.spitfire_aa) },
DeployedItem.motionalarmsensor -> { ()=> new SensorDeployable(GlobalDefinitions.motionalarmsensor) },
DeployedItem.sensor_shield -> { ()=> new SensorDeployable(GlobalDefinitions.sensor_shield) },
DeployedItem.tank_traps -> { ()=> new TrapDeployable(GlobalDefinitions.tank_traps) },
DeployedItem.portable_manned_turret -> { ()=> new TurretDeployable(GlobalDefinitions.portable_manned_turret) },
DeployedItem.portable_manned_turret -> { ()=> new TurretDeployable(GlobalDefinitions.portable_manned_turret) },
DeployedItem.portable_manned_turret_nc -> { ()=> new TurretDeployable(GlobalDefinitions.portable_manned_turret_nc) },
DeployedItem.portable_manned_turret_tr -> { ()=> new TurretDeployable(GlobalDefinitions.portable_manned_turret_tr) },
DeployedItem.portable_manned_turret_vs -> { ()=> new TurretDeployable(GlobalDefinitions.portable_manned_turret_vs) },
DeployedItem.deployable_shield_generator -> { ()=> new ShieldGeneratorDeployable(GlobalDefinitions.deployable_shield_generator) }
).withDefaultValue( { ()=> new ExplosiveDeployable(GlobalDefinitions.boomer) } )
}
}

View file

@ -0,0 +1,16 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
import net.psforever.objects.ce.SimpleDeployable
import net.psforever.objects.definition.DeployableDefinition
class ExplosiveDeployable(cdef : DeployableDefinition) extends SimpleDeployable(cdef) {
private var exploded : Boolean = false
def Exploded : Boolean = exploded
def Exploded_=(fuse : Boolean) : Boolean = {
exploded = fuse
Exploded
}
}

View file

@ -2,9 +2,9 @@
package net.psforever.objects
import net.psforever.objects.ballistics.Projectiles
import net.psforever.objects.ce.{DeployableCategory, DeployedItem}
import net.psforever.objects.definition._
import net.psforever.objects.definition.converter._
import net.psforever.objects.equipment.CItem.DeployedItem
import net.psforever.objects.equipment._
import net.psforever.objects.inventory.InventoryTile
import net.psforever.objects.serverobject.doors.DoorDefinition
@ -15,10 +15,10 @@ import net.psforever.objects.serverobject.pad.VehicleSpawnPadDefinition
import net.psforever.objects.serverobject.terminals._
import net.psforever.objects.serverobject.tube.SpawnTubeDefinition
import net.psforever.objects.serverobject.resourcesilo.ResourceSiloDefinition
import net.psforever.objects.serverobject.turret.{MannedTurretDefinition, TurretUpgrade}
import net.psforever.objects.serverobject.turret.{TurretDefinition, TurretUpgrade}
import net.psforever.objects.vehicles.{DestroyedVehicle, SeatArmorRestriction, UtilityType}
import net.psforever.objects.vital.DamageType
import net.psforever.types.PlanetSideEmpire
import net.psforever.objects.vital.{DamageType, StandardResolutions}
import net.psforever.types.{CertificationType, PlanetSideEmpire}
import scala.collection.mutable
import scala.concurrent.duration._
@ -497,6 +497,12 @@ object GlobalDefinitions {
val bullet_150mm = AmmoBoxDefinition(Ammo.bullet_150mm)
val phalanx_ammo = AmmoBoxDefinition(Ammo.phalanx_ammo)
val spitfire_ammo = AmmoBoxDefinition(Ammo.spitfire_ammo)
val spitfire_aa_ammo = AmmoBoxDefinition(Ammo.spitfire_aa_ammo)
val energy_gun_ammo = AmmoBoxDefinition(Ammo.energy_gun_ammo)
init_ammo()
val chainblade = ToolDefinition(ObjectClass.chainblade)
@ -622,6 +628,8 @@ object GlobalDefinitions {
val bank = ToolDefinition(ObjectClass.bank)
val boomer_trigger = SimpleItemDefinition(SItem.boomer_trigger)
val remote_electronics_kit = SimpleItemDefinition(SItem.remote_electronics_kit)
val trek = ToolDefinition(ObjectClass.trek)
@ -630,9 +638,9 @@ object GlobalDefinitions {
val command_detonater = SimpleItemDefinition(SItem.command_detonater)
val ace = ConstructionItemDefinition(CItem.Unit.ace)
val ace = ConstructionItemDefinition(CItem.ace)
val advanced_ace = ConstructionItemDefinition(CItem.Unit.advanced_ace)
val advanced_ace = ConstructionItemDefinition(CItem.advanced_ace)
val fury_weapon_systema = ToolDefinition(ObjectClass.fury_weapon_systema)
@ -737,6 +745,18 @@ object GlobalDefinitions {
val phalanx_avcombo = ToolDefinition(ObjectClass.phalanx_avcombo)
val phalanx_flakcombo = ToolDefinition(ObjectClass.phalanx_flakcombo)
val spitfire_weapon = ToolDefinition(ObjectClass.spitfire_weapon)
val spitfire_aa_weapon = ToolDefinition(ObjectClass.spitfire_aa_weapon)
val energy_gun = ToolDefinition(ObjectClass.energy_gun)
val energy_gun_nc = ToolDefinition(ObjectClass.energy_gun_nc)
val energy_gun_tr = ToolDefinition(ObjectClass.energy_gun_tr)
val energy_gun_vs = ToolDefinition(ObjectClass.energy_gun_vs)
init_tools()
/*
@ -809,6 +829,38 @@ object GlobalDefinitions {
val phantasm = VehicleDefinition(ObjectClass.phantasm)
init_vehicles()
/*
combat engineering deployables
*/
val boomer = DeployableDefinition(DeployedItem.boomer)
val he_mine = DeployableDefinition(DeployedItem.he_mine)
val jammer_mine = DeployableDefinition(DeployedItem.jammer_mine)
val spitfire_turret = TurretDeployableDefinition(DeployedItem.spitfire_turret)
val spitfire_cloaked = TurretDeployableDefinition(DeployedItem.spitfire_cloaked)
val spitfire_aa = TurretDeployableDefinition(DeployedItem.spitfire_aa)
val motionalarmsensor = DeployableDefinition(DeployedItem.motionalarmsensor)
val sensor_shield = DeployableDefinition(DeployedItem.sensor_shield)
val tank_traps = DeployableDefinition(DeployedItem.tank_traps)
val portable_manned_turret = TurretDeployableDefinition(DeployedItem.portable_manned_turret)
val portable_manned_turret_nc = TurretDeployableDefinition(DeployedItem.portable_manned_turret_nc)
val portable_manned_turret_tr = TurretDeployableDefinition(DeployedItem.portable_manned_turret_tr)
val portable_manned_turret_vs = TurretDeployableDefinition(DeployedItem.portable_manned_turret_vs)
val deployable_shield_generator = new ShieldGeneratorDefinition
init_deployables()
/*
Miscellaneous
*/
@ -868,7 +920,7 @@ object GlobalDefinitions {
val secondary_capture = new CaptureTerminalDefinition(751) // Tower CC
val manned_turret = new MannedTurretDefinition(480) {
val manned_turret = new TurretDefinition(480) {
Name = "manned_turret"
MaxHealth = 3600
Weapons += 1 -> new mutable.HashMap()
@ -1084,6 +1136,15 @@ object GlobalDefinitions {
}
}
def PortableMannedTurret(faction :PlanetSideEmpire.Value) : TurretDeployableDefinition = {
faction match {
case PlanetSideEmpire.TR => portable_manned_turret_tr
case PlanetSideEmpire.NC => portable_manned_turret_nc
case PlanetSideEmpire.VS => portable_manned_turret_vs
case PlanetSideEmpire.NEUTRAL => portable_manned_turret
}
}
/**
* Using the definition for a piece of `Equipment` determine if it is a grenade-type weapon.
* Only the normal grenades count; the grenade packs are excluded.
@ -1541,8 +1602,16 @@ object GlobalDefinitions {
bullet_150mm.Tile = InventoryTile.Tile44
phalanx_ammo.Name = "phalanx_ammo"
phalanx_ammo.Capacity = 4000 //sufficient for a reload
phalanx_ammo.Size = EquipmentSize.Inventory
spitfire_ammo.Name = "spitfire_ammo"
spitfire_ammo.Size = EquipmentSize.Inventory
spitfire_aa_ammo.Name = "spitfire_aa_ammo"
spitfire_aa_ammo.Size = EquipmentSize.Inventory
energy_gun_ammo.Name = "energy_gun_ammo"
energy_gun_ammo.Size = EquipmentSize.Inventory
}
/**
@ -3938,6 +4007,10 @@ object GlobalDefinitions {
remote_electronics_kit.Packet = new REKConverter
remote_electronics_kit.Tile = InventoryTile.Tile33
boomer_trigger.Name = "boomer_trigger"
boomer_trigger.Packet = new BoomerTriggerConverter
boomer_trigger.Tile = InventoryTile.Tile22
trek.Name = "trek"
trek.Size = EquipmentSize.Pistol
trek.AmmoTypes += trek_ammo
@ -3961,21 +4034,30 @@ object GlobalDefinitions {
command_detonater.Tile = InventoryTile.Tile33
ace.Name = "ace"
ace.Modes += DeployedItem.boomer
ace.Modes += DeployedItem.he_mine
ace.Modes += DeployedItem.jammer_mine
ace.Modes += DeployedItem.spitfire_turret
ace.Modes += DeployedItem.spitfire_cloaked
ace.Modes += DeployedItem.spitfire_aa
ace.Modes += DeployedItem.motionalarmsensor
ace.Modes += DeployedItem.sensor_shield
ace.Size = EquipmentSize.Pistol
ace.Modes += new ConstructionFireMode
ace.Modes.head.Item(DeployedItem.boomer -> Set(CertificationType.CombatEngineering))
ace.Modes += new ConstructionFireMode
ace.Modes(1).Item(DeployedItem.he_mine -> Set(CertificationType.CombatEngineering))
ace.Modes(1).Item(DeployedItem.jammer_mine -> Set(CertificationType.AssaultEngineering))
ace.Modes += new ConstructionFireMode
ace.Modes(2).Item(DeployedItem.spitfire_turret -> Set(CertificationType.CombatEngineering))
ace.Modes(2).Item(DeployedItem.spitfire_cloaked -> Set(CertificationType.FortificationEngineering))
ace.Modes(2).Item(DeployedItem.spitfire_aa -> Set(CertificationType.FortificationEngineering))
ace.Modes += new ConstructionFireMode
ace.Modes(3).Item(DeployedItem.motionalarmsensor -> Set(CertificationType.CombatEngineering))
ace.Modes(3).Item(DeployedItem.sensor_shield -> Set(CertificationType.AdvancedHacking, CertificationType.CombatEngineering))
ace.Tile = InventoryTile.Tile33
advanced_ace.Name = "advanced_ace"
advanced_ace.Modes += DeployedItem.tank_traps
advanced_ace.Modes += DeployedItem.portable_manned_turret
advanced_ace.Modes += DeployedItem.deployable_shield_generator
advanced_ace.Tile = InventoryTile.Tile63
advanced_ace.Size = EquipmentSize.Rifle
advanced_ace.Modes += new ConstructionFireMode
advanced_ace.Modes.head.Item(DeployedItem.tank_traps -> Set(CertificationType.FortificationEngineering))
advanced_ace.Modes += new ConstructionFireMode
advanced_ace.Modes(1).Item(DeployedItem.portable_manned_turret -> Set(CertificationType.AssaultEngineering))
advanced_ace.Modes += new ConstructionFireMode
advanced_ace.Modes(2).Item(DeployedItem.deployable_shield_generator -> Set(CertificationType.AssaultEngineering))
advanced_ace.Tile = InventoryTile.Tile93
fury_weapon_systema.Name = "fury_weapon_systema"
fury_weapon_systema.Size = EquipmentSize.VehicleWeapon
@ -4510,6 +4592,61 @@ object GlobalDefinitions {
phalanx_flakcombo.FireModes(1).ProjectileTypeIndices += 1
phalanx_flakcombo.FireModes(1).AmmoSlotIndex = 0
phalanx_flakcombo.FireModes(1).Magazine = 4000
spitfire_weapon.Name = "spitfire_weapon"
spitfire_weapon.Size = EquipmentSize.BaseTurretWeapon
spitfire_weapon.AmmoTypes += spitfire_ammo
spitfire_weapon.ProjectileTypes += spitfire_ammo_projectile
spitfire_weapon.FireModes += new InfiniteFireModeDefinition
spitfire_weapon.FireModes.head.AmmoTypeIndices += 0
spitfire_weapon.FireModes.head.AmmoSlotIndex = 0
spitfire_weapon.FireModes.head.Magazine = 4000
spitfire_aa_weapon.Name = "spitfire_aa_weapon"
spitfire_aa_weapon.Size = EquipmentSize.BaseTurretWeapon
spitfire_aa_weapon.AmmoTypes += spitfire_aa_ammo
spitfire_aa_weapon.ProjectileTypes += spitfire_aa_ammo_projectile
spitfire_aa_weapon.FireModes += new InfiniteFireModeDefinition
spitfire_aa_weapon.FireModes.head.AmmoTypeIndices += 0
spitfire_aa_weapon.FireModes.head.AmmoSlotIndex = 0
spitfire_aa_weapon.FireModes.head.Magazine = 4000
energy_gun.Name = "energy_gun"
energy_gun.Size = EquipmentSize.BaseTurretWeapon
energy_gun.AmmoTypes += energy_gun_ammo
energy_gun.ProjectileTypes += bullet_9mm_projectile //fallback
energy_gun.FireModes += new FireModeDefinition
energy_gun.FireModes.head.AmmoTypeIndices += 0
energy_gun.FireModes.head.AmmoSlotIndex = 0
energy_gun.FireModes.head.Magazine = 4000
energy_gun_nc.Name = "energy_gun_nc"
energy_gun_nc.Size = EquipmentSize.BaseTurretWeapon
energy_gun_nc.AmmoTypes += energy_gun_ammo
energy_gun_nc.ProjectileTypes += energy_gun_nc_projectile
energy_gun_nc.FireModes += new PelletFireModeDefinition
energy_gun_nc.FireModes.head.AmmoTypeIndices += 0
energy_gun_nc.FireModes.head.AmmoSlotIndex = 0
energy_gun_nc.FireModes.head.Magazine = 35
energy_gun_nc.FireModes.head.Chamber = 9
energy_gun_tr.Name = "energy_gun_tr"
energy_gun_tr.Size = EquipmentSize.BaseTurretWeapon
energy_gun_tr.AmmoTypes += energy_gun_ammo
energy_gun_tr.ProjectileTypes += energy_gun_tr_projectile
energy_gun_tr.FireModes += new FireModeDefinition
energy_gun_tr.FireModes.head.AmmoTypeIndices += 0
energy_gun_tr.FireModes.head.AmmoSlotIndex = 0
energy_gun_tr.FireModes.head.Magazine = 200
energy_gun_vs.Name = "energy_gun_vs"
energy_gun_vs.Size = EquipmentSize.BaseTurretWeapon
energy_gun_vs.AmmoTypes += energy_gun_ammo
energy_gun_vs.ProjectileTypes += energy_gun_tr_projectile
energy_gun_vs.FireModes += new FireModeDefinition
energy_gun_vs.FireModes.head.AmmoTypeIndices += 0
energy_gun_vs.FireModes.head.AmmoSlotIndex = 0
energy_gun_vs.FireModes.head.Magazine = 100
}
/**
@ -5238,4 +5375,145 @@ object GlobalDefinitions {
phantasm.Packet = variantConverter
phantasm.DestroyedModel = None //the adb calls out a phantasm_destroyed but no such asset exists
}
/**
* Initialize `Deployable` globals.
*/
private def init_deployables() : Unit = {
boomer.Name = "boomer"
boomer.Descriptor = "Boomers"
boomer.MaxHealth = 100
boomer.DeployCategory = DeployableCategory.Boomers
boomer.DeployTime = Duration.create(1000, "ms")
boomer.Model = StandardResolutions.SimpleDeployables
he_mine.Name = "he_mine"
he_mine.Descriptor = "Mines"
he_mine.MaxHealth = 100
he_mine.DeployCategory = DeployableCategory.Mines
he_mine.DeployTime = Duration.create(1000, "ms")
he_mine.Model = StandardResolutions.SimpleDeployables
jammer_mine.Name = "jammer_mine"
jammer_mine.Descriptor = "JammerMines"
jammer_mine.MaxHealth = 100
jammer_mine.DeployCategory = DeployableCategory.Mines
jammer_mine.DeployTime = Duration.create(1000, "ms")
jammer_mine.Model = StandardResolutions.SimpleDeployables
spitfire_turret.Name = "spitfire_turret"
spitfire_turret.Descriptor= "Spitfires"
spitfire_turret.MaxHealth = 100
spitfire_turret.Weapons += 1 -> new mutable.HashMap()
spitfire_turret.Weapons(1) += TurretUpgrade.None -> spitfire_weapon
spitfire_turret.ReserveAmmunition = false
spitfire_turret.DeployCategory = DeployableCategory.SmallTurrets
spitfire_turret.DeployTime = Duration.create(5000, "ms")
spitfire_turret.Model = StandardResolutions.ComplexDeployables
spitfire_cloaked.Name = "spitfire_cloaked"
spitfire_cloaked.Descriptor= "CloakingSpitfires"
spitfire_cloaked.MaxHealth = 100
spitfire_cloaked.Weapons += 1 -> new mutable.HashMap()
spitfire_cloaked.Weapons(1) += TurretUpgrade.None -> spitfire_weapon
spitfire_cloaked.ReserveAmmunition = false
spitfire_cloaked.DeployCategory = DeployableCategory.SmallTurrets
spitfire_cloaked.DeployTime = Duration.create(5000, "ms")
spitfire_cloaked.Model = StandardResolutions.ComplexDeployables
spitfire_aa.Name = "spitfire_aa"
spitfire_aa.Descriptor= "FlakSpitfires"
spitfire_aa.MaxHealth = 100
spitfire_aa.Weapons += 1 -> new mutable.HashMap()
spitfire_aa.Weapons(1) += TurretUpgrade.None -> spitfire_aa_weapon
spitfire_aa.ReserveAmmunition = false
spitfire_aa.DeployCategory = DeployableCategory.SmallTurrets
spitfire_aa.DeployTime = Duration.create(5000, "ms")
spitfire_aa.Model = StandardResolutions.ComplexDeployables
motionalarmsensor.Name = "motionalarmsensor"
motionalarmsensor.Descriptor = "MotionSensors"
motionalarmsensor.MaxHealth = 100
motionalarmsensor.DeployCategory = DeployableCategory.Sensors
motionalarmsensor.DeployTime = Duration.create(1000, "ms")
motionalarmsensor.Model = StandardResolutions.SimpleDeployables
sensor_shield.Name = "sensor_shield"
sensor_shield.Descriptor = "SensorShields"
sensor_shield.MaxHealth = 100
sensor_shield.DeployCategory = DeployableCategory.Sensors
sensor_shield.DeployTime = Duration.create(5000, "ms")
sensor_shield.Model = StandardResolutions.SimpleDeployables
tank_traps.Name = "tank_traps"
tank_traps.Descriptor = "TankTraps"
tank_traps.MaxHealth = 5000
tank_traps.Packet = new TRAPConverter
tank_traps.DeployCategory = DeployableCategory.TankTraps
tank_traps.DeployTime = Duration.create(6000, "ms")
tank_traps.Model = StandardResolutions.SimpleDeployables
val fieldTurretConverter = new FieldTurretConverter
portable_manned_turret.Name = "portable_manned_turret"
portable_manned_turret.Descriptor = "FieldTurrets"
portable_manned_turret.MaxHealth = 1000
portable_manned_turret.MountPoints += 1 -> 0
portable_manned_turret.MountPoints += 2 -> 0
portable_manned_turret.Weapons += 1 -> new mutable.HashMap()
portable_manned_turret.Weapons(1) += TurretUpgrade.None -> energy_gun
portable_manned_turret.ReserveAmmunition = true
portable_manned_turret.FactionLocked = true
portable_manned_turret.Packet = fieldTurretConverter
portable_manned_turret.DeployCategory = DeployableCategory.FieldTurrets
portable_manned_turret.DeployTime = Duration.create(6000, "ms")
portable_manned_turret.Model = StandardResolutions.ComplexDeployables
portable_manned_turret_nc.Name = "portable_manned_turret_nc"
portable_manned_turret_nc.Descriptor = "FieldTurrets"
portable_manned_turret_nc.MaxHealth = 1000
portable_manned_turret_nc.MountPoints += 1 -> 0
portable_manned_turret_nc.MountPoints += 2 -> 0
portable_manned_turret_nc.Weapons += 1 -> new mutable.HashMap()
portable_manned_turret_nc.Weapons(1) += TurretUpgrade.None -> energy_gun_nc
portable_manned_turret_nc.ReserveAmmunition = true
portable_manned_turret_nc.FactionLocked = true
portable_manned_turret_nc.Packet = fieldTurretConverter
portable_manned_turret_nc.DeployCategory = DeployableCategory.FieldTurrets
portable_manned_turret_nc.DeployTime = Duration.create(6000, "ms")
portable_manned_turret_nc.Model = StandardResolutions.ComplexDeployables
portable_manned_turret_tr.Name = "portable_manned_turret_tr"
portable_manned_turret_tr.Descriptor = "FieldTurrets"
portable_manned_turret_tr.MaxHealth = 1000
portable_manned_turret_tr.MountPoints += 1 -> 0
portable_manned_turret_tr.MountPoints += 2 -> 0
portable_manned_turret_tr.Weapons += 1 -> new mutable.HashMap()
portable_manned_turret_tr.Weapons(1) += TurretUpgrade.None -> energy_gun_tr
portable_manned_turret_tr.ReserveAmmunition = true
portable_manned_turret_tr.FactionLocked = true
portable_manned_turret_tr.Packet = fieldTurretConverter
portable_manned_turret_tr.DeployCategory = DeployableCategory.FieldTurrets
portable_manned_turret_tr.DeployTime = Duration.create(6000, "ms")
portable_manned_turret_tr.Model = StandardResolutions.ComplexDeployables
portable_manned_turret_vs.Name = "portable_manned_turret_vs"
portable_manned_turret_vs.Descriptor = "FieldTurrets"
portable_manned_turret_vs.MaxHealth = 1000
portable_manned_turret_vs.MountPoints += 1 -> 0
portable_manned_turret_vs.MountPoints += 2 -> 0
portable_manned_turret_vs.Weapons += 1 -> new mutable.HashMap()
portable_manned_turret_vs.Weapons(1) += TurretUpgrade.None -> energy_gun_vs
portable_manned_turret_vs.ReserveAmmunition = true
portable_manned_turret_vs.FactionLocked = true
portable_manned_turret_vs.Packet = fieldTurretConverter
portable_manned_turret_vs.DeployCategory = DeployableCategory.FieldTurrets
portable_manned_turret_vs.DeployTime = Duration.create(6000, "ms")
portable_manned_turret_vs.Model = StandardResolutions.ComplexDeployables
deployable_shield_generator.Name = "deployable_shield_generator"
deployable_shield_generator.Descriptor = "ShieldGenerators"
deployable_shield_generator.MaxHealth = 1700
deployable_shield_generator.DeployTime = Duration.create(6000, "ms")
deployable_shield_generator.Model = StandardResolutions.ComplexDeployables
}
}

View file

@ -0,0 +1,9 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
import net.psforever.objects.ce.SimpleDeployable
import net.psforever.objects.definition.DeployableDefinition
import net.psforever.objects.serverobject.hackable.Hackable
class SensorDeployable(cdef : DeployableDefinition) extends SimpleDeployable(cdef)
with Hackable

View file

@ -0,0 +1,15 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
import net.psforever.objects.ce.{ComplexDeployable, DeployableCategory}
import net.psforever.objects.definition.DeployableDefinition
import net.psforever.objects.definition.converter.ShieldGeneratorConverter
import net.psforever.objects.serverobject.hackable.Hackable
class ShieldGeneratorDeployable(cdef : ShieldGeneratorDefinition) extends ComplexDeployable(cdef)
with Hackable
class ShieldGeneratorDefinition extends DeployableDefinition(240) {
Packet = new ShieldGeneratorConverter
DeployCategory = DeployableCategory.ShieldGenerators
}

View file

@ -8,7 +8,7 @@ import net.psforever.objects.ballistics.Projectiles
import scala.annotation.tailrec
/**
* A type of utility that can be wielded and loaded with certain other game elements.<br>
* A type of `Equipment` that can be wielded and loaded with certain other game elements.<br>
* <br>
* "Tool" is a very mechanical name while this class is intended for various weapons and support items.
* The primary trait of a `Tool` is that it has something that counts as an "ammunition,"
@ -17,7 +17,8 @@ import scala.annotation.tailrec
* Some weapons Chainblade have ammunition but do not consume it.
* @param toolDef the `ObjectDefinition` that constructs this item and maintains some of its immutable fields
*/
class Tool(private val toolDef : ToolDefinition) extends Equipment with FireModeSwitch[FireModeDefinition] {
class Tool(private val toolDef : ToolDefinition) extends Equipment
with FireModeSwitch[FireModeDefinition] {
/** index of the current fire mode on the `ToolDefinition`'s list of fire modes */
private var fireModeIndex : Int = 0
/** current ammunition slot being used by this fire mode */

View file

@ -0,0 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
import net.psforever.objects.ce.SimpleDeployable
import net.psforever.objects.definition.DeployableDefinition
class TrapDeployable(cdef : DeployableDefinition) extends SimpleDeployable(cdef)

View file

@ -0,0 +1,89 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
import akka.actor.{Actor, ActorContext, Props}
import net.psforever.objects.ce.{Deployable, DeployedItem}
import net.psforever.objects.definition.{BaseDeployableDefinition, DeployableDefinition}
import net.psforever.objects.definition.converter.SmallTurretConverter
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.mount.MountableBehavior
import net.psforever.objects.serverobject.turret.{TurretDefinition, WeaponTurret}
class TurretDeployable(tdef : TurretDeployableDefinition) extends PlanetSideServerObject
with Deployable
with WeaponTurret
with Hackable {
private var shields : Int = 0
WeaponTurret.LoadDefinition(this) //calls the equivalent of Health = Definition.MaxHealth
def MaxHealth : Int = Definition.MaxHealth
def Shields : Int = shields
def Shields_=(toShields : Int) : Int = {
shields = math.min(math.max(0, toShields), MaxShields)
Shields
}
def MaxShields : Int = {
0//Definition.MaxShields
}
def MountPoints : Map[Int, Int] = Definition.MountPoints.toMap
//override to clarify inheritance conflict
override def Health : Int = super[Deployable].Health
//override to clarify inheritance conflict
override def Health_=(toHealth : Int) : Int = super[Deployable].Health_=(toHealth)
override def Definition = tdef
}
class TurretDeployableDefinition(private val objectId : Int) extends TurretDefinition(objectId)
with BaseDeployableDefinition {
private val item = DeployedItem(objectId) //let throw NoSuchElementException
Name = "turret_deployable"
Packet = new SmallTurretConverter
def Item : DeployedItem.Value = item
//override to clarify inheritance conflict
override def MaxHealth : Int = super[BaseDeployableDefinition].MaxHealth
//override to clarify inheritance conflict
override def MaxHealth_=(max : Int) : Int = super[BaseDeployableDefinition].MaxHealth_=(max)
override def Initialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) = {
obj.Actor = context.actorOf(Props(classOf[TurretControl], obj), s"${obj.Definition.Name}_${obj.GUID.guid}")
}
override def Uninitialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) = {
DeployableDefinition.SimpleUninitialize(obj, context)
}
}
object TurretDeployableDefinition {
def apply(dtype : DeployedItem.Value) : TurretDeployableDefinition = {
new TurretDeployableDefinition(dtype.id)
}
}
/** control actors */
class TurretControl(turret : TurretDeployable) extends Actor
with FactionAffinityBehavior.Check
with MountableBehavior.Mount
with MountableBehavior.Dismount {
def MountableObject = turret //do not add type!
def FactionObject : FactionAffinity = turret
def receive : Receive = checkBehavior
.orElse(dismountBehavior)
.orElse(turretMountBehavior)
.orElse {
case _ => ;
}
}

View file

@ -0,0 +1,668 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.avatar
import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.ce.{Deployable, DeployableCategory, DeployedItem}
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.CertificationType
import scala.collection.mutable
/**
* A class that keeps track - "manages" - deployables that are owned by the avatar.<br>
* <br>
* Deployables belong to the Engineering certification line of certifications.
* `CombatEngineering` and above certifications include permissions for different types of deployables,
* and one unique type of deployable is available through the `GroundSupport`
* and one that also requires `AdvancedHacking`.
* (They are collectively called "ce" for that reason.)
* Not only does the level of certification change the maximum number of deployables that can be managed by type
* but it also influences the maximum number of deployables that can be managed by category.
* Individual deployables are counted by type and category individually in special data structures
* to avoid having to probe the primary list of deployable references whenever a question of quantity is asked.
* As deployables are added and removed, and tracked certifications are added and removed,
* these structures are updated to reflect proper count.
*/
class DeployableToolbox {
/**
* a map of bins for keeping track of the quantities of deployables in a category
* keys: categories, values: quantity storage object
*/
private val categoryCounts = DeployableCategory.values.toSeq.map(value => { value -> new DeployableToolbox.Bin }).toMap
//)
/**
* a map of bins for keeping track of the quantities of individual deployables
* keys: deployable types, values: quantity storage object
*/
private val deployableCounts = DeployedItem.values.toSeq.map(value => { value -> new DeployableToolbox.Bin }).toMap
/**
* a map of tracked/owned individual deployables
* keys: categories, values: deployable objects
*/
private val deployableLists =
DeployableCategory.values.toSeq.map(value => { value -> mutable.ListBuffer[DeployableToolbox.AcceptableDeployable]() }).toMap
/**
* can only be initialized once
* set during the `Initialization` method primarily, and in `Add` and in `Remove` if not
*/
private var initialized : Boolean = false
/**
* Set up the initial deployable counts by providing certification values to be used in category and unit selection.
* @param certifications a group of certifications for the initial values
* @return `true`, if this is the first time and actual "initialization" is performed;
* `false`, otherwise
*/
def Initialize(certifications : Set[CertificationType.Value]) : Boolean = {
if(!initialized) {
DeployableToolbox.Initialize(deployableCounts, categoryCounts, certifications)
initialized = true
true
}
else {
false
}
}
/**
* Change the count of deployable units that can be tracked by providing a new certification.
* If the given certification is already factored into the quantities, no changes will occur.
* @param certification the new certification
* @param certificationSet the group of previous certifications being tracked;
* occasionally, important former certification values are required for additional configuration;
* the new certification should already have been added to this group
*/
def AddToDeployableQuantities(certification : CertificationType.Value, certificationSet : Set[CertificationType.Value]) : Unit = {
initialized = true
DeployableToolbox.AddToDeployableQuantities(deployableCounts, categoryCounts, certification, certificationSet)
}
/**
* Change the count of deployable units that can be tracked
* by designating a certification whose deployables will be removed.
* If the given certification is already factored out of the quantities, no changes will occur.
* @param certification the old certification
* @param certificationSet the group of previous certifications being tracked;
* occasionally, important former certification values are required for additional configuration;
* the new certification should already have been excluded from this group
*/
def RemoveFromDeployableQuantities(certification : CertificationType.Value, certificationSet : Set[CertificationType.Value]) : Unit = {
initialized = true
DeployableToolbox.RemoveFromDeployablesQuantities(deployableCounts, categoryCounts, certification, certificationSet)
}
/**
* Determine if the given deployable can be managed by this toolbox.
* @see `Valid`
* @see `Available`
* @see `Contains`
* @param obj the deployable
* @return `true`, if it can be managed under the current conditions;
* `false`, otherwise
*/
def Accept(obj : DeployableToolbox.AcceptableDeployable) : Boolean = {
Valid(obj) && Available(obj) && !Contains(obj)
}
/**
* Determine if the given deployable can be managed by this toolbox
* by testing if the specific deployable maximum and the deployable category maximum is non-zero
* @param obj the deployable
* @return `true`, if both category maximum and deployable type maximum are positive non-zero integers;
* `false`, otherwise
*/
def Valid(obj : DeployableToolbox.AcceptableDeployable) : Boolean = {
deployableCounts(DeployableToolbox.UnifiedType(obj.Definition.Item)).Max > 0 &&
categoryCounts(obj.Definition.DeployCategory).Max > 0
}
/**
* Determine if the given deployable can be managed by this toolbox
* by testing if the specific deployable list and the deployable category list have available slots.
* In this case, a "slot" is merely the difference between the current count is less than the maximum count.
* @param obj the deployable
* @return `true`, if the deployable can be added to the support lists and counted;
* `false`, otherwise
*/
def Available(obj : DeployableToolbox.AcceptableDeployable) : Boolean = {
deployableCounts(DeployableToolbox.UnifiedType(obj.Definition.Item)).Available &&
categoryCounts(obj.Definition.DeployCategory).Available
}
/**
* Check if this deployable is already being managed by the toolbox
* by determining whether or not it is already being managed by this toolbox.
* @param obj the deployable
* @return `true`, if the deployable can be found in one of the lists;
* `false`, otherwise
*/
def Contains(obj : DeployableToolbox.AcceptableDeployable) : Boolean = {
deployableLists(obj.Definition.DeployCategory).contains(obj)
}
/**
* Manage the provided deployable.<br>
* <br>
* Although proper testing should be performed prior to attempting to add the deployable to this toolbox,
* three tests are administered to determine whether space is available prior to insertion.
* The first two tests check for available space in the category count and in the unit count
* and the third test checks whether the deployable is already being managed by this toolbox.
* No changes should occur if the deployable is not properly added.
* @param obj the deployable
* @return `true`, if the deployable is added;
* `false`, otherwise
*/
def Add(obj : DeployableToolbox.AcceptableDeployable) : Boolean = {
val category = obj.Definition.DeployCategory
val dCategory = categoryCounts(category)
val dType = deployableCounts(DeployableToolbox.UnifiedType(obj.Definition.Item))
val dList = deployableLists(category)
if(dCategory.Available() && dType.Available() && !dList.contains(obj)) {
dCategory.Current += 1
dType.Current += 1
dList += obj
true
}
else {
false
}
}
/**
* Stop managing the provided deployable.<br>
* <br>
* Although proper testing should be performed prior to attempting to remove the deployable to this toolbox,
* a single test is administered to determine whether the removal can take place.
* If the deployable is found to currently being managed by this toolbox, then it is properly removed.
* No changes should occur if the deployable is not properly removed.
* @param obj the deployable
* @return `true`, if the deployable is added;
* `false`, otherwise
*/
def Remove(obj : DeployableToolbox.AcceptableDeployable) : Boolean = {
val category = obj.Definition.DeployCategory
val deployables = deployableLists(category)
if(deployables.contains(obj)) {
categoryCounts(category).Current -= 1
deployableCounts(DeployableToolbox.UnifiedType(obj.Definition.Item)).Current -= 1
deployables -= obj
true
}
else {
false
}
}
/**
* Remove the first managed deployable that matches the same type of deployable as the example.
* The explicit tests is defined to find the first deployable whose type matches.
* @param obj the example deployable
* @return any deployable that is found
*/
def DisplaceFirst(obj : DeployableToolbox.AcceptableDeployable) : Option[DeployableToolbox.AcceptableDeployable] = {
DisplaceFirst(obj, { d => d.Definition.Item == obj.Definition.Item })
}
/**
* Remove the first managed deployable that satisfies a test and belongs to the same category as the example.
* The test in question is used to pinpoint the first qualifying deployable;
* but, if the test fails to find any valid targets,
* the first deployable in the list of managed deployables for that category is selected to be removed.
* The only test performed is whether there is any valid deployable managed for the category.
* @param obj the example deployable
* @param rule the testing rule for determining a valid deployable
* @return any deployable that is found
*/
def DisplaceFirst(obj : DeployableToolbox.AcceptableDeployable, rule : (Deployable)=> Boolean) : Option[DeployableToolbox.AcceptableDeployable] = {
val definition = obj.Definition
val category = definition.DeployCategory
val categoryList = deployableLists(category)
if(categoryList.nonEmpty) {
val found = categoryList.find(rule) match {
case Some(target) =>
categoryList.remove(categoryList.indexOf(target))
case None =>
categoryList.remove(0)
}
categoryCounts(category).Current -= 1
deployableCounts(DeployableToolbox.UnifiedType(found.Definition.Item)).Current -= 1
Some(found)
}
else {
None
}
}
/**
* Remove the first managed deployable from a category.
* The only test performed is whether there is any valid deployable managed for the category.
* @param category the target category
* @return any deployable that is found
*/
def DisplaceFirst(category : DeployableCategory.Value) : Option[DeployableToolbox.AcceptableDeployable] = {
val categoryList = deployableLists(category)
if(categoryList.nonEmpty) {
val found = categoryList.remove(0)
categoryCounts(category).Current -= 1
deployableCounts(DeployableToolbox.UnifiedType(found.Definition.Item)).Current -= 1
Some(found)
}
else {
None
}
}
/**
* Reference all managed deployables of the same type as an example deployable.
* @param filter the example deployable
* @return a list of globally unique identifiers that should be valid for the current zone
*/
def Deployables(filter : DeployableToolbox.AcceptableDeployable) : List[PlanetSideGUID] = {
Deployables(filter.Definition.Item)
}
/**
* Reference all managed deployables of the same type.
* @param filter the type of deployable
* @return a list of globally unique identifiers that should be valid for the current zone
*/
def Deployables(filter : DeployedItem.Value) : List[PlanetSideGUID] = {
deployableLists(Deployable.Category.Of(filter))
.filter(entry => { entry.Definition.Item == filter })
.map(_.GUID).toList
}
/**
* Reference all managed deployables in the same category as an example deployable.
* @param filter the example deployable
* @return a list of globally unique identifiers that should be valid for the current zone
*/
def Category(filter : DeployableToolbox.AcceptableDeployable) : List[PlanetSideGUID] = {
Category(filter.Definition.DeployCategory)
}
/**
* Reference all managed deployables in the same category.
* @param filter the type of deployable
* @return a list of globally unique identifiers that should be valid for the current zone
*/
def Category(filter : DeployableCategory.Value) : List[PlanetSideGUID] = {
deployableLists(filter).map(_.GUID).toList
}
/**
* Check the current capacity for the same type of deployable as the example.
* @param item the example deployable
* @return the current quantity of deployables and the maximum number
*/
def CountDeployable(item : DeployedItem.Value) : (Int, Int) = {
val dType = deployableCounts(DeployableToolbox.UnifiedType(item))
(dType.Current, dType.Max)
}
/**
* Check the current capacity for the same category of deployable as the example.
* @param item the example deployable
* @return the current quantity of deployables and the maximum number
*/
def CountCategory(item : DeployedItem.Value) : (Int, Int) = {
val dCat = categoryCounts(Deployable.Category.Of(DeployableToolbox.UnifiedType(item)))
(dCat.Current, dCat.Max)
}
def UpdateUIElement(entry : DeployedItem.Value) : List[(Int,Int,Int,Int)] = {
val toEntry = DeployableToolbox.UnifiedType(entry)
val (curr, max) = Deployable.UI(toEntry)
val dType = deployableCounts(toEntry)
List((curr, dType.Current, max, dType.Max))
}
def UpdateUI() : List[(Int,Int,Int,Int)] = DeployedItem.values flatMap UpdateUIElement toList
def UpdateUI(entry : CertificationType.Value) : List[(Int,Int,Int,Int)] = {
import CertificationType._
entry match {
case AdvancedHacking =>
UpdateUIElement(DeployedItem.sensor_shield)
case CombatEngineering =>
List(
DeployedItem.boomer, DeployedItem.he_mine, DeployedItem.spitfire_turret, DeployedItem.motionalarmsensor
) flatMap UpdateUIElement
case AssaultEngineering =>
List(
DeployedItem.jammer_mine, DeployedItem.portable_manned_turret, DeployedItem.deployable_shield_generator
) flatMap UpdateUIElement
case FortificationEngineering =>
List(
DeployedItem.boomer,
DeployedItem.he_mine,
DeployedItem.spitfire_turret, DeployedItem.spitfire_cloaked, DeployedItem.spitfire_aa,
DeployedItem.motionalarmsensor,
DeployedItem.tank_traps
) flatMap UpdateUIElement
case AdvancedEngineering =>
List(AssaultEngineering, FortificationEngineering) flatMap UpdateUI
case _ =>
Nil
}
}
def UpdateUI(certifications : List[CertificationType.Value]) : List[(Int,Int,Int,Int)] = {
certifications flatMap UpdateUI
}
/**
* Remove all managed deployables that are the same type.
* @param item the deployable type
* @return a list of globally unique identifiers that should be valid for the current zone
*/
def ClearDeployable(item : DeployedItem.Value) : List[PlanetSideGUID] = {
val uitem = DeployableToolbox.UnifiedType(item)
val category = Deployable.Category.Of(uitem)
val categoryList = deployableLists(category)
val (out, in) = categoryList.partition(_.Definition.Item == item)
categoryList.clear()
categoryList ++= in
categoryCounts(category).Current = in.size
deployableCounts(uitem).Current = 0
out.map(_.GUID).toList
}
/**
* Remove all managed deployables that belong to the same category.
* @param item the deployable type belonging to a category
* @return a list of globally unique identifiers that should be valid for the current zone
*/
def ClearCategory(item : DeployedItem.Value) : List[PlanetSideGUID] = {
val category = Deployable.Category.Of(DeployableToolbox.UnifiedType(item))
val out = deployableLists(category).map(_.GUID).toList
deployableLists(category).clear()
categoryCounts(category).Current = 0
(Deployable.Category.Includes(category) map DeployableToolbox.UnifiedType toSet)
.foreach({item : DeployedItem.Value => deployableCounts(item).Current = 0 })
out
}
/**
* Remove all managed deployables.
* @return a list of globally unique identifiers that should be valid for the current zone
*/
def Clear() : List[PlanetSideGUID] = {
val out = deployableLists.values.flatten.map(_.GUID).toList
deployableLists.values.foreach(_.clear())
deployableCounts.values.foreach(_.Current = 0)
categoryCounts.values.foreach(_.Current = 0)
out
}
}
object DeployableToolbox {
/**
* A `type` intended to properly define the minimum acceptable conditions for a `Deployable` object.
*/
type AcceptableDeployable = PlanetSideGameObject with Deployable
/**
* An internal class to keep track of the quantity of deployables managed for a certain set of criteria.
* There are deployable numbers organized by deploybale type and by deployable category.
*/
private class Bin {
/** the maximum number of deployables for this criteria that can be managed */
private var max : Int = 0
/** the current number of deployables for this criteria that are being managed */
private var current : Int = 0
def Current : Int = current
def Current_=(curr : Int) : Int = {
current = curr
Current
}
def Max : Int = max
def Max_=(mx : Int) : Int = {
max = mx
Max
}
def Available() : Boolean = current < max
}
/**
* Some deployable types, though unique themselves,
* resolve to the same deployable type for the purposes of categorization.
* @param item the type of deployable
* @return the corrected deployable type
*/
def UnifiedType(item : DeployedItem.Value) : DeployedItem.Value = item match {
case DeployedItem.portable_manned_turret_nc | DeployedItem.portable_manned_turret_tr | DeployedItem.portable_manned_turret_vs =>
DeployedItem.portable_manned_turret
case _ =>
item
}
/**
* Hardcoded maximum values for the category and type initialization.
* @param counts a reference to the type `Bin` object
* @param categories a reference to the category `Bin` object
* @param certifications a group of certifications for the initial values
*/
private def Initialize(counts : Map[DeployedItem.Value, DeployableToolbox.Bin], categories : Map[DeployableCategory.Value, DeployableToolbox.Bin], certifications : Set[CertificationType.Value]) : Unit = {
import CertificationType._
if(certifications.contains(AdvancedEngineering)) {
counts(DeployedItem.boomer).Max = 25
counts(DeployedItem.he_mine).Max = 25
counts(DeployedItem.jammer_mine).Max = 20
counts(DeployedItem.spitfire_turret).Max = 15
counts(DeployedItem.spitfire_cloaked).Max = 5
counts(DeployedItem.spitfire_aa).Max = 5
counts(DeployedItem.motionalarmsensor).Max = 25
counts(DeployedItem.tank_traps).Max = 5
counts(DeployedItem.portable_manned_turret).Max = 1 //the below turret types are unified
//counts(DeployedItem.portable_manned_turret_nc).Max = 1
//counts(DeployedItem.portable_manned_turret_tr).Max = 1
//counts(DeployedItem.portable_manned_turret_vs).Max = 1
counts(DeployedItem.deployable_shield_generator).Max = 1
categories(DeployableCategory.Boomers).Max = 25
categories(DeployableCategory.Mines).Max = 25
categories(DeployableCategory.SmallTurrets).Max = 15
categories(DeployableCategory.Sensors).Max = 25
categories(DeployableCategory.TankTraps).Max = 5
categories(DeployableCategory.FieldTurrets).Max = 1
categories(DeployableCategory.ShieldGenerators).Max = 1
if(certifications.contains(AdvancedHacking)) {
counts(DeployedItem.sensor_shield).Max = 25
}
}
else if(certifications.contains(CombatEngineering)) {
if(certifications.contains(AssaultEngineering)) {
counts(DeployedItem.jammer_mine).Max = 20
counts(DeployedItem.portable_manned_turret).Max = 1 //the below turret types are unified
//counts(DeployedItem.portable_manned_turret_nc).Max = 1
//counts(DeployedItem.portable_manned_turret_tr).Max = 1
//counts(DeployedItem.portable_manned_turret_vs).Max = 1
counts(DeployedItem.deployable_shield_generator).Max = 1
categories(DeployableCategory.FieldTurrets).Max = 1
categories(DeployableCategory.ShieldGenerators).Max = 1
}
if(certifications.contains(FortificationEngineering)) {
counts(DeployedItem.boomer).Max = 25
counts(DeployedItem.he_mine).Max = 25
counts(DeployedItem.spitfire_turret).Max = 15
counts(DeployedItem.spitfire_cloaked).Max = 5
counts(DeployedItem.spitfire_aa).Max = 5
counts(DeployedItem.motionalarmsensor).Max = 25
counts(DeployedItem.tank_traps).Max = 5
categories(DeployableCategory.Boomers).Max = 25
categories(DeployableCategory.Mines).Max = 25
categories(DeployableCategory.SmallTurrets).Max = 15
categories(DeployableCategory.Sensors).Max = 25
categories(DeployableCategory.TankTraps).Max = 5
}
else {
counts(DeployedItem.boomer).Max = 20
counts(DeployedItem.he_mine).Max = 20
counts(DeployedItem.spitfire_turret).Max = 10
counts(DeployedItem.motionalarmsensor).Max = 20
categories(DeployableCategory.Boomers).Max = 20
categories(DeployableCategory.Mines).Max = 20
categories(DeployableCategory.SmallTurrets).Max = 10
categories(DeployableCategory.Sensors).Max = 20
}
if(certifications.contains(AdvancedHacking)) {
counts(DeployedItem.sensor_shield).Max = 20
}
}
if(certifications.contains(CertificationType.GroundSupport)) {
counts(DeployedItem.router_telepad_deployable).Max = 1
categories(DeployableCategory.Telepads).Max = 1
}
}
/**
* Hardcoded maximum values for the category and type initialization upon providing a new certification.
* @param counts a reference to the type `Bin` object
* @param categories a reference to the category `Bin` object
* @param certification the new certification
* @param certificationSet the group of previous certifications being tracked
*/
def AddToDeployableQuantities(counts : Map[DeployedItem.Value, DeployableToolbox.Bin], categories : Map[DeployableCategory.Value, DeployableToolbox.Bin], certification : CertificationType.Value, certificationSet : Set[CertificationType.Value]) : Unit = {
import CertificationType._
if(certificationSet contains certification) {
certification match {
case AdvancedHacking =>
if(certificationSet contains CombatEngineering) {
counts(DeployedItem.sensor_shield).Max = 20
}
case CombatEngineering =>
counts(DeployedItem.boomer).Max = 20
counts(DeployedItem.he_mine).Max = 20
counts(DeployedItem.spitfire_turret).Max = 10
counts(DeployedItem.motionalarmsensor).Max = 20
categories(DeployableCategory.Boomers).Max = 20
categories(DeployableCategory.Mines).Max = 20
categories(DeployableCategory.SmallTurrets).Max = 10
categories(DeployableCategory.Sensors).Max = 20
if(certificationSet contains AdvancedHacking) {
counts(DeployedItem.sensor_shield).Max = 20
}
case AssaultEngineering =>
counts(DeployedItem.jammer_mine).Max = 20
counts(DeployedItem.portable_manned_turret).Max = 1 //the below turret types are unified
//counts(DeployedItem.portable_manned_turret_nc).Max = 1
//counts(DeployedItem.portable_manned_turret_tr).Max = 1
//counts(DeployedItem.portable_manned_turret_vs).Max = 1
counts(DeployedItem.deployable_shield_generator).Max = 1
categories(DeployableCategory.FieldTurrets).Max = 1
categories(DeployableCategory.ShieldGenerators).Max = 1
case FortificationEngineering =>
counts(DeployedItem.boomer).Max = 25
counts(DeployedItem.he_mine).Max = 25
counts(DeployedItem.spitfire_turret).Max = 15
counts(DeployedItem.motionalarmsensor).Max = 25
counts(DeployedItem.spitfire_cloaked).Max = 5
counts(DeployedItem.spitfire_aa).Max = 5
counts(DeployedItem.tank_traps).Max = 5
categories(DeployableCategory.Boomers).Max = 25
categories(DeployableCategory.Mines).Max = 25
categories(DeployableCategory.SmallTurrets).Max = 15
categories(DeployableCategory.Sensors).Max = 25
categories(DeployableCategory.TankTraps).Max = 5
case AdvancedEngineering =>
if(!certificationSet.contains(AssaultEngineering)) {
AddToDeployableQuantities(counts, categories, AssaultEngineering, certificationSet ++ Set(AssaultEngineering))
}
if(!certificationSet.contains(FortificationEngineering)) {
AddToDeployableQuantities(counts, categories, FortificationEngineering, certificationSet ++ Set(FortificationEngineering))
}
case GroundSupport =>
counts(DeployedItem.router_telepad_deployable).Max = 1024
categories(DeployableCategory.Telepads).Max = 1024
case _ => ;
}
}
}
/**
* Hardcoded zero'd values for the category and type initialization upon ignoring a previous certification.
* @param counts a reference to the type `Bin` object
* @param categories a reference to the category `Bin` object
* @param certification the new certification
* @param certificationSet the group of previous certifications being tracked
*/
def RemoveFromDeployablesQuantities(counts : Map[DeployedItem.Value, DeployableToolbox.Bin], categories : Map[DeployableCategory.Value, DeployableToolbox.Bin], certification : CertificationType.Value, certificationSet : Set[CertificationType.Value]) : Unit = {
import CertificationType._
if(!certificationSet.contains(certification)) {
certification match {
case AdvancedHacking =>
counts(DeployedItem.sensor_shield).Max = 0
case CombatEngineering =>
counts(DeployedItem.boomer).Max = 0
counts(DeployedItem.he_mine).Max = 0
counts(DeployedItem.spitfire_turret).Max = 0
counts(DeployedItem.motionalarmsensor).Max = 0
counts(DeployedItem.sensor_shield).Max = 0
categories(DeployableCategory.Boomers).Max = 0
categories(DeployableCategory.Mines).Max = 0
categories(DeployableCategory.SmallTurrets).Max = 0
categories(DeployableCategory.Sensors).Max = 0
case AssaultEngineering =>
counts(DeployedItem.jammer_mine).Max = 0
counts(DeployedItem.portable_manned_turret).Max = 0 //the below turret types are unified
//counts(DeployedItem.portable_manned_turret_nc).Max = 0
//counts(DeployedItem.portable_manned_turret_tr).Max = 0
//counts(DeployedItem.portable_manned_turret_vs).Max = 0
counts(DeployedItem.deployable_shield_generator).Max = 0
categories(DeployableCategory.Sensors).Max = if(certificationSet contains CombatEngineering) 20 else 0
categories(DeployableCategory.FieldTurrets).Max = 0
categories(DeployableCategory.ShieldGenerators).Max = 0
case FortificationEngineering =>
val ce : Int = if(certificationSet contains CombatEngineering) 1 else 0 //true = 1, false = 0
counts(DeployedItem.boomer).Max = ce * 20
counts(DeployedItem.he_mine).Max = ce * 20
counts(DeployedItem.spitfire_turret).Max = ce * 10
counts(DeployedItem.motionalarmsensor).Max = ce * 20
counts(DeployedItem.spitfire_cloaked).Max = 0
counts(DeployedItem.spitfire_aa).Max = 0
counts(DeployedItem.tank_traps).Max = 0
categories(DeployableCategory.Boomers).Max = ce * 20
categories(DeployableCategory.Mines).Max = ce * 20
categories(DeployableCategory.SmallTurrets).Max = ce * 10
categories(DeployableCategory.Sensors).Max = ce * 20
categories(DeployableCategory.TankTraps).Max = 0
case AdvancedEngineering =>
if(!certificationSet.contains(AssaultEngineering)) {
RemoveFromDeployablesQuantities(counts, categories, AssaultEngineering, certificationSet)
}
if(!certificationSet.contains(FortificationEngineering)) {
RemoveFromDeployablesQuantities(counts, categories, FortificationEngineering, certificationSet)
}
case GroundSupport =>
counts(DeployedItem.router_telepad_deployable).Max = 0
categories(DeployableCategory.Telepads).Max = 0
case _ => ;
}
}
}
}

View file

@ -0,0 +1,51 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.ballistics
import net.psforever.objects.TurretDeployable
import net.psforever.objects.ce.ComplexDeployable
import net.psforever.objects.definition.{BaseDeployableDefinition, ObjectDefinition}
import net.psforever.types.{PlanetSideEmpire, Vector3}
final case class ComplexDeployableSource(obj_def : ObjectDefinition with BaseDeployableDefinition,
faction : PlanetSideEmpire.Value,
health : Int,
shields : Int,
ownerName : String,
position : Vector3,
orientation : Vector3) extends SourceEntry {
override def Name = SourceEntry.NameFormat(obj_def.Name)
override def Faction = faction
def Definition : ObjectDefinition with BaseDeployableDefinition = obj_def
def Health = health
def Shields = shields
def OwnerName = ownerName
def Position = position
def Orientation = orientation
def Velocity = None
}
object ComplexDeployableSource {
def apply(obj : ComplexDeployable) : ComplexDeployableSource = {
ComplexDeployableSource(
obj.Definition,
obj.Faction,
obj.Health,
obj.Shields,
obj.OwnerName.getOrElse(""),
obj.Position,
obj.Orientation
)
}
def apply(obj : TurretDeployable) : ComplexDeployableSource = {
ComplexDeployableSource(
obj.Definition,
obj.Faction,
obj.Health,
obj.Shields,
obj.OwnerName.getOrElse(""),
obj.Position,
obj.Orientation
)
}
}

View file

@ -0,0 +1,36 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.ballistics
import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.ce.Deployable
import net.psforever.objects.definition.{BaseDeployableDefinition, ObjectDefinition}
import net.psforever.types.{PlanetSideEmpire, Vector3}
final case class DeployableSource(obj_def : ObjectDefinition with BaseDeployableDefinition,
faction : PlanetSideEmpire.Value,
health : Int,
ownerName : String,
position : Vector3,
orientation : Vector3) extends SourceEntry {
override def Name = SourceEntry.NameFormat(obj_def.Name)
override def Faction = faction
def Definition : ObjectDefinition with BaseDeployableDefinition = obj_def
def Health = health
def OwnerName = ownerName
def Position = position
def Orientation = orientation
def Velocity = None
}
object DeployableSource {
def apply(obj : PlanetSideGameObject with Deployable) : DeployableSource = {
DeployableSource(
obj.Definition,
obj.Faction,
obj.Health,
obj.OwnerName.getOrElse(""),
obj.Position,
obj.Orientation
)
}
}

View file

@ -1,8 +1,9 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.ballistics
import net.psforever.objects.ce.{ComplexDeployable, SimpleDeployable}
import net.psforever.objects.definition.ObjectDefinition
import net.psforever.objects.{PlanetSideGameObject, Player, Vehicle}
import net.psforever.objects.{PlanetSideGameObject, Player, TurretDeployable, Vehicle}
import net.psforever.objects.entity.WorldEntity
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.types.{PlanetSideEmpire, Vector3}
@ -29,6 +30,9 @@ object SourceEntry {
target match {
case obj : Player => PlayerSource(obj)
case obj : Vehicle => VehicleSource(obj)
case obj : ComplexDeployable => ComplexDeployableSource(obj)
case obj : TurretDeployable => ComplexDeployableSource(obj)
case obj : SimpleDeployable => DeployableSource(obj)
case _ => ObjectSource(target)
}
}

View file

@ -0,0 +1,27 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.ce
import net.psforever.objects.definition.DeployableDefinition
import net.psforever.objects.serverobject.PlanetSideServerObject
abstract class ComplexDeployable(cdef : DeployableDefinition) extends PlanetSideServerObject
with Deployable {
Health = Definition.MaxHealth
def MaxHealth : Int = Definition.MaxHealth
private var shields : Int = 0
def Shields : Int = shields
def Shields_=(toShields : Int) : Int = {
shields = math.min(math.max(0, toShields), MaxShields)
Shields
}
def MaxShields : Int = {
0//Definition.MaxShields
}
def Definition = cdef
}

View file

@ -0,0 +1,151 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.ce
import net.psforever.objects._
import net.psforever.objects.definition.{BaseDeployableDefinition, ObjectDefinition}
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.vital.{DamageResistanceModel, Vitality}
import net.psforever.packet.game.{DeployableIcon, PlanetSideGUID}
import net.psforever.types.PlanetSideEmpire
trait Deployable extends FactionAffinity
with Vitality {
this : PlanetSideGameObject =>
private var health : Int = 1
private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
private var owner : Option[PlanetSideGUID] = None
private var ownerName : Option[String] = None
def Health : Int = health
def Health_=(toHealth : Int) : Int = {
health = math.min(math.max(0, toHealth), MaxHealth)
Health
}
def MaxHealth : Int
def Faction : PlanetSideEmpire.Value = faction
override def Faction_=(toFaction : PlanetSideEmpire.Value) : PlanetSideEmpire.Value = {
faction = toFaction
Faction
}
def Owner : Option[PlanetSideGUID] = owner
def Owner_=(owner : PlanetSideGUID) : Option[PlanetSideGUID] = Owner_=(Some(owner))
def Owner_=(owner : Player) : Option[PlanetSideGUID] = Owner_=(Some(owner.GUID))
def Owner_=(owner : Option[PlanetSideGUID]) : Option[PlanetSideGUID] = {
owner match {
case Some(_) =>
this.owner = owner
case None =>
this.owner = None
}
Owner
}
def OwnerName : Option[String] = ownerName
def OwnerName_=(owner : String) : Option[String] = OwnerName_=(Some(owner))
def OwnerName_=(owner : Player) : Option[String] = OwnerName_=(Some(owner.Name))
def OwnerName_=(owner : Option[String]) : Option[String] = {
owner match {
case Some(_) =>
ownerName = owner
case None =>
ownerName = None
}
OwnerName
}
def DamageModel : DamageResistanceModel = Definition.asInstanceOf[DamageResistanceModel]
def Definition : ObjectDefinition with BaseDeployableDefinition
}
object Deployable {
object Category {
def Of(item : DeployedItem.Value) : DeployableCategory.Value = deployablesToCategories(item)
def Includes(category : DeployableCategory.Value) : List[DeployedItem.Value] = {
(for {
(ce, cat) <- deployablesToCategories
if cat == category
} yield ce) toList
}
def OfAll() : Map[DeployedItem.Value, DeployableCategory.Value] = deployablesToCategories
private val deployablesToCategories : Map[DeployedItem.Value, DeployableCategory.Value] = Map(
DeployedItem.boomer -> DeployableCategory.Boomers,
DeployedItem.he_mine -> DeployableCategory.Mines,
DeployedItem.jammer_mine -> DeployableCategory.Mines,
DeployedItem.spitfire_turret -> DeployableCategory.SmallTurrets,
DeployedItem.motionalarmsensor -> DeployableCategory.Sensors,
DeployedItem.spitfire_cloaked -> DeployableCategory.SmallTurrets,
DeployedItem.spitfire_aa -> DeployableCategory.SmallTurrets,
DeployedItem.deployable_shield_generator -> DeployableCategory.ShieldGenerators,
DeployedItem.tank_traps -> DeployableCategory.TankTraps,
DeployedItem.portable_manned_turret -> DeployableCategory.FieldTurrets,
DeployedItem.portable_manned_turret_nc -> DeployableCategory.FieldTurrets,
DeployedItem.portable_manned_turret_tr -> DeployableCategory.FieldTurrets,
DeployedItem.portable_manned_turret_vs -> DeployableCategory.FieldTurrets,
DeployedItem.sensor_shield -> DeployableCategory.Sensors,
DeployedItem.router_telepad_deployable -> DeployableCategory.Telepads
)
}
object Icon {
def apply(item : DeployedItem.Value) : DeployableIcon.Value = ceicon(item.id)
private val ceicon : Map[Int, DeployableIcon.Value] = Map(
DeployedItem.boomer.id -> DeployableIcon.Boomer,
DeployedItem.he_mine.id -> DeployableIcon.HEMine,
DeployedItem.jammer_mine.id -> DeployableIcon.DisruptorMine,
DeployedItem.spitfire_turret.id -> DeployableIcon.SpitfireTurret,
DeployedItem.spitfire_cloaked.id -> DeployableIcon.ShadowTurret,
DeployedItem.spitfire_aa.id -> DeployableIcon.CerebusTurret,
DeployedItem.motionalarmsensor.id -> DeployableIcon.MotionAlarmSensor,
DeployedItem.sensor_shield.id -> DeployableIcon.SensorDisruptor,
DeployedItem.tank_traps.id -> DeployableIcon.TRAP,
DeployedItem.portable_manned_turret.id -> DeployableIcon.FieldTurret,
DeployedItem.portable_manned_turret_tr.id -> DeployableIcon.FieldTurret,
DeployedItem.portable_manned_turret_nc.id -> DeployableIcon.FieldTurret,
DeployedItem.portable_manned_turret_vs.id -> DeployableIcon.FieldTurret,
DeployedItem.deployable_shield_generator.id -> DeployableIcon.AegisShieldGenerator
).withDefaultValue(DeployableIcon.Boomer)
}
object UI {
def apply(item : DeployedItem.Value) : (Int, Int) = planetsideAttribute(item)
/**
* The attribute values to be invoked in `PlanetsideAttributeMessage` packets
* in reference to a particular combat engineering deployable element on the UI.
* The first number is for the actual count field.
* The second number is for the maximum count field.
*/
private val planetsideAttribute : Map[DeployedItem.Value, (Int, Int)] = Map(
DeployedItem.boomer -> (94, 83),
DeployedItem.he_mine -> (95, 84),
DeployedItem.jammer_mine -> (96, 85),
DeployedItem.spitfire_turret -> (97, 86),
DeployedItem.motionalarmsensor -> (98, 87),
DeployedItem.spitfire_cloaked -> (99, 88),
DeployedItem.spitfire_aa -> (100, 89),
DeployedItem.deployable_shield_generator -> (101, 90),
DeployedItem.tank_traps -> (102, 91),
DeployedItem.portable_manned_turret -> (103, 92),
DeployedItem.portable_manned_turret_nc -> (103, 92),
DeployedItem.portable_manned_turret_tr -> (103, 92),
DeployedItem.portable_manned_turret_vs -> (103, 92),
DeployedItem.sensor_shield -> (104, 93)
).withDefaultValue((0,0))
}
}

View file

@ -0,0 +1,17 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.ce
object DeployableCategory extends Enumeration {
type Type = Value
val
Boomers,
Mines,
SmallTurrets,
Sensors,
TankTraps,
FieldTurrets,
ShieldGenerators,
Telepads
= Value
}

View file

@ -0,0 +1,22 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.ce
object DeployedItem extends Enumeration {
type Type = Value
final val boomer = Value(148)
final val deployable_shield_generator = Value(240)
final val he_mine = Value(388)
final val jammer_mine = Value(420) //disruptor mine
final val motionalarmsensor = Value(575)
final val sensor_shield = Value(752) //sensor disruptor
final val spitfire_aa = Value(819) //cerebus turret
final val spitfire_cloaked = Value(825) //shadow turret
final val spitfire_turret = Value(826)
final val tank_traps = Value(849) //trap
final val portable_manned_turret = Value(685)
final val portable_manned_turret_nc = Value(686)
final val portable_manned_turret_tr = Value(687)
final val portable_manned_turret_vs = Value(688)
final val router_telepad_deployable = Value(744)
}

View file

@ -0,0 +1,14 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.ce
import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.definition.DeployableDefinition
abstract class SimpleDeployable(cdef : DeployableDefinition) extends PlanetSideGameObject
with Deployable {
Health = Definition.MaxHealth
def MaxHealth : Int = Definition.MaxHealth
def Definition = cdef
}

View file

@ -3,6 +3,7 @@ package net.psforever.objects.definition
abstract class BasicDefinition {
private var name : String = "definition"
private var descriptor : Option[String] = None
def Name : String = name
@ -10,4 +11,13 @@ abstract class BasicDefinition {
this.name = name
Name
}
def Descriptor : String = descriptor.getOrElse(Name)
def Descriptor_=(description : String) : String = Descriptor_=(Some(description))
def Descriptor_=(description : Option[String]) : String = {
descriptor = description
Descriptor
}
}

View file

@ -1,15 +1,19 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.definition
import net.psforever.objects.ce.DeployedItem
import net.psforever.objects.definition.converter.ACEConverter
import net.psforever.objects.equipment.CItem
import net.psforever.types.CertificationType
import scala.collection.mutable.ListBuffer
class ConstructionItemDefinition(objectId : Int) extends EquipmentDefinition(objectId) {
CItem.Unit(objectId) //let throw NoSuchElementException
private val modes : ListBuffer[CItem.DeployedItem.Value] = ListBuffer()
CItem(objectId) //let throw NoSuchElementException
private val modes : ListBuffer[ConstructionFireMode] = ListBuffer()
Packet = new ACEConverter
def Modes : ListBuffer[CItem.DeployedItem.Value] = modes
def Modes : ListBuffer[ConstructionFireMode] = modes
}
object ConstructionItemDefinition {
@ -17,7 +21,29 @@ object ConstructionItemDefinition {
new ConstructionItemDefinition(objectId)
}
def apply(cItem : CItem.Unit.Value) : ConstructionItemDefinition = {
def apply(cItem : CItem.Value) : ConstructionItemDefinition = {
new ConstructionItemDefinition(cItem.id)
}
}
class ConstructionFireMode {
private val deployables : ListBuffer[DeployedItem.Value] = ListBuffer.empty
private val permissions : ListBuffer[Set[CertificationType.Value]] = ListBuffer.empty
def Permissions : ListBuffer[Set[CertificationType.Value]] = permissions
def Deployables : ListBuffer[DeployedItem.Value] = deployables
def Item(deployable : DeployedItem.Value) : ListBuffer[DeployedItem.Value] = {
deployables += deployable
permissions += Set.empty[CertificationType.Value]
deployables
}
def Item(deployPair : (DeployedItem.Value, Set[CertificationType.Value])) : ListBuffer[DeployedItem.Value] = {
val (deployable, permission) = deployPair
deployables += deployable
permissions += permission
deployables
}
}

View file

@ -0,0 +1,72 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.definition
import akka.actor.{ActorContext, ActorRef}
import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.ce.{Deployable, DeployableCategory, DeployedItem}
import net.psforever.objects.definition.converter.SmallDeployableConverter
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.vital.{DamageResistanceModel, NoResistanceSelection, StandardDeployableDamage}
import scala.concurrent.duration._
trait BaseDeployableDefinition extends DamageResistanceModel {
private var category : DeployableCategory.Value = DeployableCategory.Boomers
private var deployTime : Long = (1 second).toMillis //ms
private var maxHealth : Int = 1
Damage = StandardDeployableDamage
Resistance = NoResistanceSelection
def Item : DeployedItem.Value
def MaxHealth : Int = maxHealth
def MaxHealth_=(toHealth : Int) : Int = {
maxHealth = toHealth
MaxHealth
}
def DeployCategory : DeployableCategory.Value = category
def DeployCategory_=(cat : DeployableCategory.Value) : DeployableCategory.Value = {
category = cat
DeployCategory
}
def DeployTime : Long = deployTime
def DeployTime_=(time : FiniteDuration) : Long = DeployTime_=(time.toMillis)
def DeployTime_=(time: Long) : Long = {
deployTime = time
DeployTime
}
def Initialize(obj : PlanetSideGameObject with Deployable, context : ActorContext) : Unit = { }
def Initialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) : Unit = { }
def Uninitialize(obj : PlanetSideGameObject with Deployable, context : ActorContext) : Unit = { }
def Uninitialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) : Unit = { }
}
class DeployableDefinition(private val objectId : Int) extends ObjectDefinition(objectId)
with BaseDeployableDefinition {
private val item = DeployedItem(objectId) //let throw NoSuchElementException
Packet = new SmallDeployableConverter
def Item : DeployedItem.Value = item
}
object DeployableDefinition {
def apply(item : DeployedItem.Value) : DeployableDefinition =
new DeployableDefinition(item.id)
def SimpleUninitialize(obj : PlanetSideGameObject, context : ActorContext) : Unit = { }
def SimpleUninitialize(obj : PlanetSideServerObject, context : ActorContext) : Unit = {
obj.Actor ! akka.actor.PoisonPill
obj.Actor = ActorRef.noSender
}
}

View file

@ -0,0 +1,69 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.definition.converter
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.serverobject.turret.WeaponTurret
import net.psforever.objects.TurretDeployable
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate._
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
if(health > 3) {
Success(
OneMannedFieldTurretData(
SmallDeployableData(
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)
}
),
health,
Some(InventoryData(FieldTurretConverter.MakeMountings(obj)))
)
)
}
else {
Success(
OneMannedFieldTurretData(
SmallDeployableData(
PlacementData(obj.Position, obj.Orientation),
obj.Faction,
bops = false,
destroyed = true,
unk1 = 0,
jammered = false,
unk2 = false,
owner_guid = PlanetSideGUID(0)
),
0,
None
)
)
}
}
override def DetailedConstructorData(obj : TurretDeployable) : Try[OneMannedFieldTurretData] =
Failure(new Exception("converter should not be used to generate detailed OneMannedFieldTurretData"))
}
object FieldTurretConverter {
private def MakeMountings(obj : WeaponTurret) : List[InventoryItemData.InventoryItem] = {
obj.Weapons.map({
case((index, slot)) =>
val equip : Equipment = slot.Equipment.get
val equipDef = equip.Definition
InventoryItemData(equipDef.ObjectId, equip.GUID, index, equipDef.Packet.ConstructorData(equip).get)
}).toList
}
}

View file

@ -0,0 +1,31 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.definition.converter
import net.psforever.objects.Player
import net.psforever.objects.vehicles.Seat
import net.psforever.packet.game.objectcreate.{InventoryItemData, ObjectClass, PlayerData, VehicleData}
object SeatConverter {
def MakeSeat(player : Player, offset : Long) : PlayerData = {
VehicleData.PlayerData(
AvatarConverter.MakeAppearanceData(player),
AvatarConverter.MakeCharacterData(player),
AvatarConverter.MakeInventoryData(player),
AvatarConverter.GetDrawnSlot(player),
offset
)
}
//TODO do not use for now; causes seat access permission issues with many passengers; may not mesh with workflows; GUID requirements
def MakeSeats(seats : Map[Int, Seat], initialOffset : Long) : List[InventoryItemData.InventoryItem] = {
var offset = initialOffset
seats
.filter({ case (_, seat) => seat.isOccupied })
.map({case (index, seat) =>
val player = seat.Occupant.get
val entry = InventoryItemData(ObjectClass.avatar, player.GUID, index, SeatConverter.MakeSeat(player, offset))
offset += entry.bitsize
entry
}).toList
}
}

View file

@ -0,0 +1,52 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.definition.converter
import net.psforever.objects.ShieldGeneratorDeployable
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate._
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
if(health > 0) {
Success(
AegisShieldGeneratorData(
CommonFieldData(
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)
}
),
health
)
)
}
else {
Success(
AegisShieldGeneratorData(
CommonFieldData(
PlacementData(obj.Position, obj.Orientation),
obj.Faction,
bops = false,
destroyed = true,
unk = 0,
jammered = false,
player_guid = PlanetSideGUID(0)
),
0
)
)
}
}
override def DetailedConstructorData(obj : ShieldGeneratorDeployable) : Try[AegisShieldGeneratorData] =
Failure(new Exception("converter should not be used to generate detailed ShieldGeneratorDdata"))
}

View file

@ -0,0 +1,32 @@
// Copyright (c) 2017 PSForever
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 scala.util.{Failure, Success, Try}
class SmallDeployableConverter extends ObjectCreateConverter[PlanetSideGameObject with Deployable]() {
override def ConstructorData(obj : PlanetSideGameObject with Deployable) : Try[SmallDeployableData] = {
Success(
SmallDeployableData(
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)
}
)
)
}
override def DetailedConstructorData(obj : PlanetSideGameObject with Deployable) : Try[SmallDeployableData] =
Failure(new Exception("converter should not be used to generate detailed SmallDeployableData"))
}

View file

@ -0,0 +1,69 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.definition.converter
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.TurretDeployable
import net.psforever.objects.serverobject.turret.WeaponTurret
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate._
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
if(health > 0) {
Success(
SmallTurretData(
SmallDeployableData(
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)
}
),
health,
Some(InventoryData(SmallTurretConverter.MakeMountings(obj)))
)
)
}
else {
Success(
SmallTurretData(
SmallDeployableData(
PlacementData(obj.Position, obj.Orientation),
obj.Faction,
bops = false,
destroyed = true,
unk1 = 0,
jammered = false,
unk2 = false,
owner_guid = PlanetSideGUID(0)
),
0,
None
)
)
}
}
override def DetailedConstructorData(obj : TurretDeployable) : Try[SmallTurretData] =
Failure(new Exception("converter should not be used to generate detailed SmallTurretData"))
}
object SmallTurretConverter {
private def MakeMountings(obj : WeaponTurret) : List[InventoryItemData.InventoryItem] = {
obj.Weapons.map({
case((index, slot)) =>
val equip : Equipment = slot.Equipment.get
val equipDef = equip.Definition
InventoryItemData(equipDef.ObjectId, equip.GUID, index, equipDef.Packet.ConstructorData(equip).get)
}).toList
}
}

View file

@ -0,0 +1,54 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.definition.converter
import net.psforever.objects.TrapDeployable
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate._
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
if(health > 0) {
Success(
TRAPData(
SmallDeployableData(
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)
}
),
health
)
)
}
else {
Success(
TRAPData(
SmallDeployableData(
PlacementData(obj.Position, obj.Orientation),
obj.Faction,
bops = false,
destroyed = true,
unk1 = 0,
jammered = false,
unk2 = false,
owner_guid = PlanetSideGUID(0)
),
0
)
)
}
}
override def DetailedConstructorData(obj : TrapDeployable) : Try[TRAPData] =
Failure(new Exception("converter should not be used to generate detailed TRAPData"))
}

View file

@ -15,7 +15,7 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
override def ConstructorData(obj : Vehicle) : Try[VehicleData] = {
val health = 255 * obj.Health / obj.MaxHealth //TODO not precise
if(health > 3) { //active
if(health > 0) { //active
Success(
VehicleData(
PlacementData(obj.Position, obj.Orientation, obj.Velocity),
@ -72,39 +72,11 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
val offset : Long = VehicleData.InitialStreamLengthToSeatEntries(obj.Velocity.nonEmpty, SpecificFormatModifier)
obj.Seats(0).Occupant match {
case Some(player) =>
val mountedPlayer = VehicleData.PlayerData(
AvatarConverter.MakeAppearanceData(player),
AvatarConverter.MakeCharacterData(player),
AvatarConverter.MakeInventoryData(player),
AvatarConverter.GetDrawnSlot(player),
offset
)
List(InventoryItemData(ObjectClass.avatar, player.GUID, 0, mountedPlayer))
List(InventoryItemData(ObjectClass.avatar, player.GUID, 0, SeatConverter.MakeSeat(player, offset)))
case None =>
Nil
}
}
//TODO do not use for now; causes vehicle access permission issues; may not mesh with workflows; player GUID requirements
// private def MakeSeats(obj : Vehicle) : List[InventoryItemData.InventoryItem] = {
// var offset : Long = VehicleData.InitialStreamLengthToSeatEntries(obj.Velocity.nonEmpty, SpecificFormatModifier)
// obj.Seats
// .filter({ case (_, seat) => seat.isOccupied })
// .map({case (index, seat) =>
// val player = seat.Occupant.get
// val mountedPlayer = VehicleData.PlayerData(
// AvatarConverter.MakeAppearanceData(player),
// AvatarConverter.MakeCharacterData(player),
// AvatarConverter.MakeInventoryData(player),
// AvatarConverter.GetDrawnSlot(player),
// offset
// )
// val entry = InventoryItemData(ObjectClass.avatar, player.GUID, index, mountedPlayer)
// //println(s"seat 0 offset: $offset, size: ${entry.bitsize}, pad: ${mountedPlayer.basic_appearance.NamePadding}")
// offset += entry.bitsize
// entry
// }).toList
// }
private def MakeMountings(obj : Vehicle) : List[InventoryItemData.InventoryItem] = {
obj.Weapons.map({

View file

@ -1,28 +1,8 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.equipment
object CItem {
object Unit extends Enumeration {
final val ace = Value(32)
final val advanced_ace = Value(39) //fdu
final val router_telepad = Value(743)
}
object DeployedItem extends Enumeration {
final val boomer = Value(148)
final val deployable_shield_generator = Value(240)
final val he_mine = Value(388)
final val jammer_mine = Value(420) //disruptor mine
final val motionalarmsensor = Value(575)
final val sensor_shield = Value(752) //sensor disruptor
final val spitfire_aa = Value(819) //cerebus turret
final val spitfire_cloaked = Value(825) //shadow turret
final val spitfire_turret = Value(826)
final val tank_traps = Value(849) //trap
final val portable_manned_turret = Value(685)
final val portable_manned_turret_nc = Value(686)
final val portable_manned_turret_tr = Value(687)
final val portable_manned_turret_vs = Value(688)
final val router_telepad_deployable = Value(744)
}
object CItem extends Enumeration {
final val ace = Value(32)
final val advanced_ace = Value(39) //fdu
final val router_telepad = Value(743)
}

View file

@ -0,0 +1,24 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.equipment
import net.psforever.packet.game.PlanetSideGUID
trait RemoteUnit {
private var companion : Option[PlanetSideGUID] = None
def Companion : Option[PlanetSideGUID] = companion
def Companion_=(guid : PlanetSideGUID) : Option[PlanetSideGUID] = {
if(companion.isEmpty) {
companion = Some(guid)
}
Companion
}
def Companion_=(guid : Option[Any]) : Option[PlanetSideGUID] = {
if(guid.isEmpty) {
companion = None
}
Companion
}
}

View file

@ -4,8 +4,9 @@ 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.{EquipmentSlot, LockerContainer, Player, Tool, Vehicle}
import net.psforever.objects._
import net.psforever.objects.inventory.Container
import net.psforever.objects.serverobject.turret.WeaponTurret
import scala.annotation.tailrec
@ -191,6 +192,13 @@ object GUIDTask {
TaskResolver.GiveTask(RegisterObjectTask(vehicle).task, weaponTasks ++ utilTasks ++ inventoryTasks)
}
def RegisterDeployableTurret(obj : PlanetSideGameObject with WeaponTurret)(implicit guid : ActorRef) : TaskResolver.GiveTask = {
TaskResolver.GiveTask(
RegisterObjectTask(obj).task,
VisibleSlotTaskBuilding(obj.Weapons.values, GUIDTask.RegisterEquipment) ++ RegisterInventory(obj)
)
}
/**
* Construct tasking that unregisters an object from a globally unique identifier system.<br>
* <br>
@ -329,6 +337,13 @@ object GUIDTask {
TaskResolver.GiveTask(UnregisterObjectTask(vehicle).task, weaponTasks ++ utilTasks ++ inventoryTasks)
}
def UnregisterDeployableTurret(obj : PlanetSideGameObject with WeaponTurret)(implicit guid : ActorRef) : TaskResolver.GiveTask = {
TaskResolver.GiveTask(
UnregisterObjectTask(obj).task,
VisibleSlotTaskBuilding(obj.Weapons.values, GUIDTask.UnregisterEquipment) ++ UnregisterInventory(obj)
)
}
/**
* Construct tasking that allocates work upon encountered `Equipment` objects
* in reference to a globally unique identifier system of a pool of numbers.

View file

@ -99,6 +99,12 @@ object Loadout {
* @param definition the `ConstructionItemDefinition` that describes this future object
*/
final case class ShorthandConstructionItem(definition : ConstructionItemDefinition) extends Simplification
/**
* The simplified form of a `BoomerTrigger`, a unique kind of `SimpleItem`.
* @param definition the `SimpleItemDefinition` that describes this future object;
* actually ignored, but retained for function definition consistency
*/
final case class ShorthandTriggerItem(definition : SimpleItemDefinition) extends Simplification
/**
* The simplified form of a `SimpleItem`.
* @param definition the `SimpleItemDefinition` that describes this future object
@ -223,6 +229,8 @@ object Loadout {
ShorthandAmmoBox(obj.Definition, obj.Capacity)
case obj : ConstructionItem =>
ShorthandConstructionItem(obj.Definition)
case obj : BoomerTrigger =>
ShorthandTriggerItem(obj.Definition)
case obj : SimpleItem =>
ShorthandSimpleItem(obj.Definition)
case obj : Kit =>

View file

@ -2,8 +2,10 @@
package net.psforever.objects.serverobject.mount
import akka.actor.Actor
import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.entity.{Identifiable, WorldEntity}
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.turret.TurretDefinition
import net.psforever.types.Vector3
object MountableBehavior {
@ -16,7 +18,7 @@ object MountableBehavior {
trait Mount {
this : Actor =>
def MountableObject : Mountable with Identifiable with WorldEntity with FactionAffinity
def MountableObject : PlanetSideGameObject with Mountable with FactionAffinity
val mountBehavior : Receive = {
case Mountable.TryMount(user, seat_num) =>
@ -34,6 +36,25 @@ object MountableBehavior {
sender ! Mountable.MountMessages(user, Mountable.CanNotMount(obj, seat_num))
}
}
val turretMountBehavior : Receive = {
case Mountable.TryMount(user, seat_num) =>
val obj = MountableObject
val definition = obj.Definition.asInstanceOf[TurretDefinition]
obj.Seat(seat_num) match {
case Some(seat) =>
if((!definition.FactionLocked || user.Faction == obj.Faction) &&
(seat.Occupant = user).contains(user)) {
user.VehicleSeated = obj.GUID
sender ! Mountable.MountMessages(user, Mountable.CanMount(obj, seat_num))
}
else {
sender ! Mountable.MountMessages(user, Mountable.CanNotMount(obj, seat_num))
}
case None =>
sender ! Mountable.MountMessages(user, Mountable.CanNotMount(obj, seat_num))
}
}
}
/**

View file

@ -205,8 +205,8 @@ object EquipmentTerminalDefinition {
"medicalapplicator" -> MakeTool(medicalapplicator),
"bank" -> MakeTool(bank, armor_canister),
"nano_dispenser" -> MakeTool(nano_dispenser),
//TODO "ace" -> MakeConstructionItem(ace),
//TODO "advanced_ace" -> MakeConstructionItem(advanced_ace),
"ace" -> MakeConstructionItem(ace),
"advanced_ace" -> MakeConstructionItem(advanced_ace),
"remote_electronics_kit" -> MakeSimpleItem(remote_electronics_kit),
"trek" -> MakeTool(trek),
"command_detonater" -> MakeSimpleItem(command_detonater),
@ -309,6 +309,15 @@ object EquipmentTerminalDefinition {
*/
private def MakeKit(kdef : KitDefinition)() : Kit = Kit(kdef)
/**
* Create a new `BoomerTrigger`, a unique kind of `SimpleItem`.
* @param sdef the `SimpleItemDefinition` object;
* actually ignored, but retained for function definition consistency
* @return a curried function that, when called, creates the piece of `Equipment`
* @see `GlobalDefinitions`
*/
private def MakeTriggerItem(sdef : SimpleItemDefinition)() : SimpleItem = new BoomerTrigger
/**
* Create a new `SimpleItem` from provided `EquipmentDefinition` objects.
* @param sdef the `SimpleItemDefinition` object
@ -356,6 +365,9 @@ object EquipmentTerminalDefinition {
case obj : ShorthandConstructionItem =>
MakeConstructionItem(obj.definition)
case obj : ShorthandTriggerItem =>
MakeTriggerItem(obj.definition)
case obj : ShorthandSimpleItem =>
MakeSimpleItem(obj.definition)

View file

@ -0,0 +1,56 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.turret
import net.psforever.objects.serverobject.structures.Amenity
class FacilityTurret(tDef : TurretDefinition) extends Amenity
with WeaponTurret {
/** some turrets can be updated; they all start without updates */
private var upgradePath : TurretUpgrade.Value = TurretUpgrade.None
WeaponTurret.LoadDefinition(this)
def MaxHealth : Int = Definition.MaxHealth
def MountPoints : Map[Int, Int] = Definition.MountPoints.toMap
def Upgrade : TurretUpgrade.Value = upgradePath
def Upgrade_=(upgrade : TurretUpgrade.Value) : TurretUpgrade.Value = {
upgradePath = upgrade
//upgrade each weapon as long as that weapon has a valid option for that upgrade
Definition.Weapons.foreach({ case(index, upgradePaths) =>
if(upgradePaths.contains(upgrade)) {
weapons(index).Equipment.get.asInstanceOf[TurretWeapon].Upgrade = upgrade
}
})
Upgrade
}
def Definition : TurretDefinition = tDef
}
object FacilityTurret {
/**
* Overloaded constructor.
* @param tDef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
* @return a `FacilityTurret` object
*/
def apply(tDef : TurretDefinition) : FacilityTurret = {
new FacilityTurret(tDef)
}
import akka.actor.ActorContext
/**
* Instantiate and configure a `FacilityTurret` 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 `MannedTurret` object
*/
def Constructor(tdef : TurretDefinition)(id : Int, context : ActorContext) : FacilityTurret = {
import akka.actor.Props
val obj = FacilityTurret(tdef)
obj.Actor = context.actorOf(Props(classOf[FacilityTurretControl], obj), s"${tdef.Name}_$id")
obj
}
}

View file

@ -13,7 +13,7 @@ import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffi
* and faction-blind cavern sentry turrets.
* @param turret the `MannedTurret` object being governed
*/
class MannedTurretControl(turret : MannedTurret) extends Actor
class FacilityTurretControl(turret : FacilityTurret) extends Actor
with FactionAffinityBehavior.Check
with MountableBehavior.Dismount {
def MountableObject = turret //do not add type!

View file

@ -1,224 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.turret
import net.psforever.objects.{EquipmentSlot, Player}
import net.psforever.objects.definition.SeatDefinition
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.inventory.{Container, GridInventory}
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.objects.serverobject.turret.MannedTurret.MannedTurretWeapon
import net.psforever.objects.vehicles.{MountedWeapons, Seat => Chair}
class MannedTurret(tDef : MannedTurretDefinition) extends Amenity
with FactionAffinity
with Mountable
with MountedWeapons
with Container {
private var health : Int = 1
private var jammered : Boolean = false
/** manned turrets have just one seat; this is just standard interface */
private val seats : Map[Int, Chair] = Map(0 -> Chair(new SeatDefinition() { ControlledWeapon = Some(1) }))
/** manned turrets have just one weapon; this is just standard interface */
private var weapons : Map[Int, EquipmentSlot] = Map.empty
/** may or may not have inaccessible inventory space
* see `ReserveAmmunition` in the definition */
private val inventory : GridInventory = new GridInventory() {
import net.psforever.packet.game.PlanetSideGUID
override def Remove(index : Int) : Boolean = false
override def Remove(guid : PlanetSideGUID) : Boolean = false
}
/** some turrets can be updated; they all start without updates */
private var upgradePath : TurretUpgrade.Value = TurretUpgrade.None
MannedTurret.LoadDefinition(this)
def Health : Int = {
health
}
def Health_=(toHealth : Int) : Int = {
health = toHealth
health
}
def MaxHealth : Int = {
Definition.MaxHealth
}
def Seats : Map[Int, Chair] = seats
def Seat(seatNum : Int) : Option[Chair] = seats.get(seatNum)
/**
* Given the index of an entry mounting point, return the infantry-accessible `Seat` associated with it.
* @param mountPoint an index representing the seat position / mounting point
* @return a seat number, or `None`
*/
def GetSeatFromMountPoint(mountPoint : Int) : Option[Int] = {
Definition.MountPoints.get(mountPoint)
}
def MountPoints : Map[Int, Int] = Definition.MountPoints.toMap
def PassengerInSeat(user : Player) : Option[Int] = {
if(seats(0).Occupant.contains(user)) {
Some(0)
}
else {
None
}
}
def Weapons : Map[Int, EquipmentSlot] = weapons.filter({ case(index, _) => index < 2 })
def ControlledWeapon(wepNumber : Int) : Option[Equipment] = {
if(VisibleSlots.contains(wepNumber)) {
weapons(wepNumber).Equipment
}
else {
None
}
}
def Inventory : GridInventory = inventory
def VisibleSlots : Set[Int] = Set(1)
def Upgrade : TurretUpgrade.Value = upgradePath
def Upgrade_=(upgrade : TurretUpgrade.Value) : TurretUpgrade.Value = {
upgradePath = upgrade
//upgrade each weapon as long as that weapon has a valid option for that upgrade
Definition.Weapons.foreach({ case(index, upgradePaths) =>
if(upgradePaths.contains(upgrade)) {
weapons(index).Equipment.get.asInstanceOf[MannedTurretWeapon].Upgrade = upgrade
}
})
Upgrade
}
def Jammered : Boolean = jammered
def Jammered_=(jamState : Boolean) : Boolean = {
jammered = jamState
Jammered
}
def Definition : MannedTurretDefinition = tDef
}
object MannedTurret {
/**
* Overloaded constructor.
* @param tDef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
* @return a `MannedTurret` object
*/
def apply(tDef : MannedTurretDefinition) : MannedTurret = {
new MannedTurret(tDef)
}
/**
* Use the `*Definition` that was provided to this object to initialize its fields and settings.
* @param turret the `MannedTurret` being initialized
* @see `{object}.LoadDefinition`
*/
def LoadDefinition(turret : MannedTurret) : MannedTurret = {
import net.psforever.objects.equipment.EquipmentSize.BaseTurretWeapon
val tdef : MannedTurretDefinition = turret.Definition
//general stuff
turret.Health = tdef.MaxHealth
//create weapons; note the class
turret.weapons = tdef.Weapons.map({case (num, upgradePaths) =>
val slot = EquipmentSlot(BaseTurretWeapon)
slot.Equipment = new MannedTurretWeapon(tdef, upgradePaths.toMap)
num -> slot
}).toMap
//special inventory ammunition object(s)
if(tdef.ReserveAmmunition) {
val allAmmunitionTypes = tdef.Weapons.values.flatMap{ _.values.flatMap { _.AmmoTypes } }.toSet
if(allAmmunitionTypes.nonEmpty) {
turret.inventory.Resize(allAmmunitionTypes.size, 1)
var i : Int = 0
allAmmunitionTypes.foreach(ammotype => {
turret.inventory.InsertQuickly(i, new TurretAmmoBox(ammotype))
i += 1
})
}
}
turret
}
import net.psforever.objects.definition.ToolDefinition
import net.psforever.objects.Tool
/**
* A stateful weapon that is mounted in `MannedTurrets`
* and may maintains a group of upgraded forms that can by swapped
* without reconstructing the weapon object itself or managing object registration.
* @param mdef the turret's definition
* @param udefs a map of turret upgrades to tool definitions that would be constructed by this weapon
* @param default the default upgrade state;
* defaults to `None`
*/
private class MannedTurretWeapon(mdef : MannedTurretDefinition, udefs : Map[TurretUpgrade.Value, ToolDefinition], default : TurretUpgrade.Value = TurretUpgrade.None)
extends Tool(udefs(default)) {
private var upgradePath : TurretUpgrade.Value = default
def Upgrade : TurretUpgrade.Value = {
/*
Must check `not null` due to how this object's `Definition` will be called during `Tool`'s constructor
before the internal value can be set to default value `None`
*/
Option(upgradePath) match {
case Some(value) =>
value
case None =>
default
}
}
def Upgrade_=(upgrade : TurretUpgrade.Value) : TurretUpgrade.Value = {
if(udefs.contains(upgrade)) {
val beforeUpgrade = upgradePath
upgradePath = upgrade
if(beforeUpgrade != upgradePath) {
Tool.LoadDefinition(this) //rebuild weapon internal structure
FireModeIndex = 0 //reset fire mode; this option is always valid
}
}
Upgrade
}
override def Definition = udefs(Upgrade)
}
import net.psforever.objects.definition.AmmoBoxDefinition
import net.psforever.objects.AmmoBox
/**
* A special type of ammunition box contained within a `MannedTurret` for the purposes of infinite reloads.
* The original quantity of ammunition does not change.
* @param adef ammunition definition
*/
private class TurretAmmoBox(private val adef : AmmoBoxDefinition) extends AmmoBox(adef, Some(65535)) {
import net.psforever.objects.inventory.InventoryTile
override def Tile = InventoryTile.Tile11
override def Capacity_=(toCapacity : Int) = Capacity
}
import akka.actor.ActorContext
/**
* Instantiate an configure a `MannedTurret` 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 `MannedTurret` object
*/
def Constructor(tdef : MannedTurretDefinition)(id : Int, context : ActorContext) : MannedTurret = {
import akka.actor.Props
val obj = MannedTurret(tdef)
obj.Actor = context.actorOf(Props(classOf[MannedTurretControl], obj), s"${tdef.Name}_$id")
obj
}
}

View file

@ -10,7 +10,7 @@ import scala.collection.mutable
* The definition for any `MannedTurret`.
* @param objectId the object's identifier number
*/
class MannedTurretDefinition(private val objectId : Int) extends ObjectDefinition(objectId) {
class TurretDefinition(private val objectId : Int) extends ObjectDefinition(objectId) {
Turrets(objectId) //let throw NoSuchElementException
private var maxHealth : Int = 100

View file

@ -0,0 +1,177 @@
// Copyright (c) 2017 PSForever
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.inventory.{Container, GridInventory}
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.vehicles.{MountedWeapons, Seat => Chair}
trait WeaponTurret extends FactionAffinity
with Mountable
with MountedWeapons
with Container {
this : PlanetSideGameObject =>
private var health : Int = 1
private var jammered : Boolean = false
/** manned turrets have just one seat; this is just standard interface */
protected val seats : Map[Int, Chair] = Map(0 -> Chair(new SeatDefinition() { ControlledWeapon = Some(1) }))
/** turrets have just one weapon; this is just standard interface */
protected var weapons : Map[Int, EquipmentSlot] = Map.empty
/** may or may not have inaccessible inventory space
* see `ReserveAmmunition` in the definition */
protected val inventory : GridInventory = new GridInventory() {
import net.psforever.packet.game.PlanetSideGUID
override def Remove(index : Int) : Boolean = false
override def Remove(guid : PlanetSideGUID) : Boolean = false
}
def Health : Int = {
health
}
def Health_=(toHealth : Int) : Int = {
health = toHealth
health
}
def MaxHealth : Int
def Inventory : GridInventory = inventory
def VisibleSlots : Set[Int] = Set(1)
def Weapons : Map[Int, EquipmentSlot] = weapons
def MountPoints : Map[Int, Int]
def Seats : Map[Int, Chair] = seats
def Seat(seatNum : Int) : Option[Chair] = seats.get(seatNum)
/**
* Given the index of an entry mounting point, return the infantry-accessible `Seat` associated with it.
* @param mountPoint an index representing the seat position / mounting point
* @return a seat number, or `None`
*/
def GetSeatFromMountPoint(mountPoint : Int) : Option[Int] = {
MountPoints.get(mountPoint)
}
def PassengerInSeat(user : Player) : Option[Int] = {
if(seats(0).Occupant.contains(user)) {
Some(0)
}
else {
None
}
}
def ControlledWeapon(wepNumber : Int) : Option[Equipment] = {
if(VisibleSlots.contains(wepNumber)) {
weapons(wepNumber).Equipment
}
else {
None
}
}
def Jammered : Boolean = jammered
def Jammered_=(jamState : Boolean) : Boolean = {
jammered = jamState
Jammered
}
def Definition : TurretDefinition
}
object WeaponTurret {
/**
* Use the `*Definition` that was provided to this object to initialize its fields and settings.
* @see `{object}.LoadDefinition`
* @param turret the `MannedTurret` being initialized
*/
def LoadDefinition(turret : WeaponTurret) : WeaponTurret = {
LoadDefinition(turret, turret.Definition)
}
/**
* Use the `*Definition` that was provided to this object to initialize its fields and settings.
* A default definition is provided to be used.
* @see `{object}.LoadDefinition`
* @param turret the `MannedTurret` being initialized
* @param tdef the object definition
*/
def LoadDefinition(turret : WeaponTurret, tdef : TurretDefinition) : WeaponTurret = {
import net.psforever.objects.equipment.EquipmentSize.BaseTurretWeapon
//general stuff
turret.Health = tdef.MaxHealth
//create weapons; note the class
turret.weapons = tdef.Weapons.map({case (num, upgradePaths) =>
val slot = EquipmentSlot(BaseTurretWeapon)
slot.Equipment = new TurretWeapon(tdef, upgradePaths.toMap)
num -> slot
}).toMap
//special inventory ammunition object(s)
if(tdef.ReserveAmmunition) {
val allAmmunitionTypes = tdef.Weapons.values.flatMap{ _.values.flatMap { _.AmmoTypes } }.toSet
if(allAmmunitionTypes.nonEmpty) {
turret.inventory.Resize(allAmmunitionTypes.size, 1)
var i : Int = 0
allAmmunitionTypes.foreach(ammotype => {
turret.inventory.InsertQuickly(i, new TurretAmmoBox(ammotype))
i += 1
})
}
}
turret
}
}
class TurretWeapon(mdef : TurretDefinition, udefs : Map[TurretUpgrade.Value, ToolDefinition], default : TurretUpgrade.Value = TurretUpgrade.None)
extends Tool(udefs(default)) {
private var upgradePath : TurretUpgrade.Value = default
def Upgrade : TurretUpgrade.Value = {
/*
Must check `not null` due to how this object's `Definition` will be called during `Tool`'s constructor
before the internal value can be set to default value `None`
*/
Option(upgradePath) match {
case Some(value) =>
value
case None =>
default
}
}
def Upgrade_=(upgrade : TurretUpgrade.Value) : TurretUpgrade.Value = {
if(udefs.contains(upgrade)) {
val beforeUpgrade = upgradePath
upgradePath = upgrade
if(beforeUpgrade != upgradePath) {
Tool.LoadDefinition(this) //rebuild weapon internal structure
FireModeIndex = 0 //reset fire mode; this option is always valid
}
}
Upgrade
}
override def Definition = udefs(Upgrade)
}
/**
* A special type of ammunition box contained within a `MannedTurret` for the purposes of infinite reloads.
* The original quantity of ammunition does not change.
* @param adef ammunition definition
*/
class TurretAmmoBox(private val adef : AmmoBoxDefinition) extends AmmoBox(adef, Some(65535)) {
import net.psforever.objects.inventory.InventoryTile
override def Tile = InventoryTile.Tile11
override def Capacity_=(toCapacity : Int) = Capacity
}

View file

@ -2,9 +2,16 @@
package net.psforever.objects.vehicles
/**
* An `Enumeration` of all the turret type objectss in the game, paired with their object id as the `Value`.
* An `Enumeration` of all the turret type objects in the game, paired with their object id as the `Value`.
*/
object Turrets extends Enumeration {
val manned_turret = Value(480)
val vanu_sentry_turret = Value(943)
final val manned_turret = Value(480)
final val portable_manned_turret = Value(685)
final val portable_manned_turret_nc = Value(686)
final val portable_manned_turret_tr = Value(687)
final val portable_manned_turret_vs = Value(688)
final val spitfire_aa = Value(819)
final val spitfire_cloaked = Value(825)
final val spitfire_turret = Value(826)
final val vanu_sentry_turret = Value(943)
}

View file

@ -111,3 +111,9 @@ object StandardAircraftDamage extends DamageSelection {
def Splash = AircraftSplashDamage.Calculate
def Lash = AircraftLashDamage.Calculate
}
object StandardDeployableDamage extends DamageSelection {
def Direct = VehicleHitDamage.Calculate
def Splash = VehicleSplashDamage.Calculate
def Lash = NoDamage.Calculate
}

View file

@ -23,9 +23,21 @@ object VehicleResolutions extends DamageResistCalculations(
ResolutionCalculations.VehicleApplication
)
object SimpleDeployableResolutions extends DamageResistCalculations(
ResolutionCalculations.VehicleDamageAfterResist,
ResolutionCalculations.SimpleDeployableApplication
)
object ComplexDeployableResolutions extends DamageResistCalculations(
ResolutionCalculations.VehicleDamageAfterResist,
ResolutionCalculations.ComplexDeployableApplication
)
object StandardResolutions extends ResolutionSelection {
def Infantry : ResolutionCalculations.Form = InfantryResolutions.Calculate
def Max : ResolutionCalculations.Form = MaxResolutions.Calculate
def Vehicle : ResolutionCalculations.Form = VehicleResolutions.Calculate
def Aircraft : ResolutionCalculations.Form = VehicleResolutions.Calculate
def SimpleDeployables : ResolutionCalculations.Form = SimpleDeployableResolutions.Calculate
def ComplexDeployables : ResolutionCalculations.Form = ComplexDeployableResolutions.Calculate
}

View file

@ -102,6 +102,8 @@ object Vitality {
*/
final case class Damage(func : (Any)=>Unit)
final case class DamageOn(obj : Vitality, func : (Any)=>Unit)
/**
* Report that a vitals object must be updated due to damage.
* @param obj the vital object

View file

@ -18,10 +18,23 @@ abstract class DamageResistCalculations[A](calcFunc : (ResolvedProjectile)=>((In
applyFunc : (A, ResolvedProjectile)=>ResolutionCalculations.Output)
extends ResolutionCalculations {
def Calculate(damages : ProjectileCalculations.Form, resistances : ProjectileCalculations.Form, data : ResolvedProjectile) : ResolutionCalculations.Output = {
val modDam = Sample(damages, resistances, data)
applyFunc(modDam, data)
}
/**
* An intermediate step of the normal `Calculate` operation that retrieves the damage values in their transitory form.
* @param damages the function that calculations raw damage values
* @param resistances the function that calculates resistance values
* @param data a historical projectile interaction;
* the origin of the data used to extract damage and resistance values
* @return the transitory form of the modified damage(s);
* usually, a single `Int` value or a tuple of `Int` values
*/
def Sample(damages : ProjectileCalculations.Form, resistances : ProjectileCalculations.Form, data : ResolvedProjectile) : A = {
val dam : Int = damages(data)
val res : Int = resistances(data)
val mod = calcFunc(data)
val modDam = mod(dam, res)
applyFunc(modDam, data)
mod(dam, res)
}
}

View file

@ -1,8 +1,9 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.vital.resolution
import net.psforever.objects.{Player, Vehicle}
import net.psforever.objects.{Player, TurretDeployable, Vehicle}
import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile}
import net.psforever.objects.ce.{ComplexDeployable, SimpleDeployable}
import net.psforever.objects.vital.projectile.ProjectileCalculations
/**
@ -157,4 +158,49 @@ object ResolutionCalculations {
}
case _ => ;
}
def SimpleDeployableApplication(damage : Int, data : ResolvedProjectile)(target : Any) : Unit = target match {
case ce : SimpleDeployable =>
if(ce.Health > 0) {
ce.Health -= damage
ce.History(data)
}
case _ =>
}
def ComplexDeployableApplication(damage : Int, data : ResolvedProjectile)(target : Any) : Unit = target match {
case ce : ComplexDeployable =>
if(ce.Shields > 0) {
if(damage > ce.Shields) {
ce.Health -= (damage - ce.Shields)
ce.Shields = 0
}
else {
ce.Shields -= damage
}
ce.History(data)
}
else if(ce.Health > 0) {
ce.Health -= damage
ce.History(data)
}
case ce : TurretDeployable =>
if(ce.Shields > 0) {
if(damage > ce.Shields) {
ce.Health -= (damage - ce.Shields)
ce.Shields = 0
}
else {
ce.Shields -= damage
}
ce.History(data)
}
else if(ce.Health > 0) {
ce.Health -= damage
ce.History(data)
}
case _ => ;
}
}

View file

@ -5,14 +5,16 @@ import akka.actor.{ActorContext, ActorRef, Props}
import akka.routing.RandomPool
import net.psforever.objects.ballistics.Projectile
import net.psforever.objects._
import net.psforever.objects.ce.Deployable
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.guid.NumberPoolHub
import net.psforever.objects.guid.actor.UniqueNumberSystem
import net.psforever.objects.guid.selector.RandomSelector
import net.psforever.objects.guid.source.LimitedNumberSource
import net.psforever.objects.inventory.Container
import net.psforever.objects.serverobject.structures.{Amenity, Building}
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.serverobject.turret.MannedTurret
import net.psforever.objects.serverobject.turret.FacilityTurret
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.Vector3
@ -54,6 +56,10 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
/** Used by the `Zone` to coordinate `Equipment` dropping and collection requests. */
private var ground : ActorRef = ActorRef.noSender
/** */
private val constructions : ListBuffer[PlanetSideGameObject with Deployable] = ListBuffer[PlanetSideGameObject with Deployable]()
/** */
private var deployables : ActorRef = ActorRef.noSender
/** */
private var transport : ActorRef = ActorRef.noSender
/** */
private val players : TrieMap[Avatar, Option[Player]] = TrieMap[Avatar, Option[Player]]()
@ -91,6 +97,7 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
SetupNumberPools()
accessor = context.actorOf(RandomPool(25).props(Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystem.AllocateNumberPoolActors(guid))), s"$Id-uns")
ground = context.actorOf(Props(classOf[ZoneGroundActor], this, equipmentOnGround), s"$Id-ground")
deployables = context.actorOf(Props(classOf[ZoneDeployableActor], this, constructions), s"$Id-deployables")
transport = context.actorOf(Props(classOf[ZoneVehicleActor], this, vehicles), s"$Id-vehicles")
population = context.actorOf(Props(classOf[ZonePopulationActor], this, players, corpses), s"$Id-players")
@ -253,6 +260,8 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
*/
def EquipmentOnGround : List[Equipment] = equipmentOnGround.toList
def DeployableList : List[PlanetSideGameObject with Deployable] = constructions.toList
def Vehicles : List[Vehicle] = vehicles.toList
def Players : List[Avatar] = players.keys.toList
@ -271,6 +280,8 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
*/
def Ground : ActorRef = ground
def Deployables : ActorRef = deployables
def Transport : ActorRef = transport
def Population : ActorRef = population
@ -290,7 +301,7 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
//turret to weapon
Map.TurretToWeapon.foreach({ case ((turret_guid, weapon_guid)) =>
((GUID(turret_guid) match {
case Some(obj : MannedTurret) =>
case Some(obj : FacilityTurret) =>
Some(obj)
case _ => ;
None
@ -322,7 +333,11 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
private def AssignAmenities() : Unit = {
Map.ObjectToBuilding.foreach({ case(object_guid, building_id) =>
buildings(building_id).Amenities = guid(object_guid).get.asInstanceOf[Amenity]
(buildings.get(building_id), guid(object_guid)) match {
case (Some(building), Some(amenity)) =>
building.Amenities = amenity.asInstanceOf[Amenity]
case (None, _) | (_, None) => ; //let ZoneActor's sanity check catch this error
}
})
}
@ -386,6 +401,17 @@ object Zone {
/** Default value, non-zone area. */
final val Nowhere : Zone = new Zone("nowhere", new ZoneMap("nowhere"), 99)
/**
* Overloaded constructor.
* @param id the privileged name that can be used as the second parameter in the packet `LoadMapMessage`
* @param map the map of server objects upon which this `Zone` is based
* @param number the numerical index of the `Zone` as it is recognized in a variety of packets
* @return a `Zone` object
*/
def apply(id : String, map : ZoneMap, number : Int) : Zone = {
new Zone(id, map, number)
}
/**
* Message to initialize the `Zone`.
* @see `Zone.Init(implicit ActorContext)`
@ -494,6 +520,14 @@ object Zone {
final case class RemoveItem(item_guid : PlanetSideGUID)
}
object Deployable {
final case class Build(obj : PlanetSideGameObject with Deployable, withTool : ConstructionItem)
final case class DeployableIsBuilt(obj : PlanetSideGameObject with Deployable, withTool : ConstructionItem)
final case class Dismiss(obj : PlanetSideGameObject with Deployable)
final case class DeployableIsDismissed(obj : PlanetSideGameObject with Deployable)
}
object Vehicle {
final case class Spawn(vehicle : Vehicle)
@ -512,14 +546,71 @@ object Zone {
*/
final case class ClientInitialization(zone : Zone)
/**
* Overloaded constructor.
* @param id the privileged name that can be used as the second parameter in the packet `LoadMapMessage`
* @param map the map of server objects upon which this `Zone` is based
* @param number the numerical index of the `Zone` as it is recognized in a variety of packets
* @return a `Zone` object
*/
def apply(id : String, map : ZoneMap, number : Int) : Zone = {
new Zone(id, map, number)
object EquipmentIs {
/**
* Tha base `trait` connecting all `Equipment` object location tokens.
*/
sealed trait ItemLocation
/**
* The target item is contained within another object.
* @see `GridInventory`<br>
* `Container`
* @param obj the containing object
* @param index the slot where the target is located
*/
final case class InContainer(obj : Container, index : Int) extends ItemLocation
/**
* The target item is found on the Ground.
* @see `ZoneGroundActor`
*/
final case class OnGround() extends ItemLocation
/**
* The target item exists but could not be found belonging to any expected region of the location.
*/
final case class Orphaned() extends ItemLocation
/**
* An exhaustive search of the provided zone is conducted in search of the target `Equipment` object
* and a token that qualifies the current location of the object in the zone is returned.
* The following groups of objects are searched:
* the inventories of all players and all corpses,
* all vehicles trunks,
* the lockers of all players and corpses;
* and, if still not found, the ground is scoured too.
* @see `ItemLocation`<br>
* `LockerContainer`
* @param equipment the target object
* @param guid that target object's globally unique identifier
* @param continent the zone whose objects to search
* @return a token that explains where the object is, if it is found in this zone;
* `None` is the token that is used to indicate not having been found
*/
def Where(equipment : Equipment, guid : PlanetSideGUID, continent : Zone) : Option[Zone.EquipmentIs.ItemLocation] = {
continent.GUID(guid) match {
case Some(_) =>
((continent.LivePlayers ++ continent.Corpses).find(_.Find(guid).nonEmpty) match {
case Some(tplayer) => Some((tplayer, tplayer.Find(guid)))
case _ => None
}).orElse(continent.Vehicles.find(_.Find(guid).nonEmpty) match {
case Some(vehicle) => Some((vehicle, vehicle.Find(guid)))
case _ => None
}).orElse(continent.Players.find(_.Locker.Find(guid).nonEmpty) match {
case Some(avatar) => Some((avatar.Locker, avatar.Locker.Find(guid)))
case _ => None
}) match {
case Some((obj, Some(index))) =>
Some(Zone.EquipmentIs.InContainer(obj, index))
case _ =>
continent.EquipmentOnGround.find(_.GUID == guid) match {
case Some(_) =>
Some(Zone.EquipmentIs.OnGround())
case None =>
Some(Zone.EquipmentIs.Orphaned())
}
}
case None =>
None
}
}
}
}

View file

@ -56,6 +56,13 @@ class ZoneActor(zone : Zone) extends Actor {
case msg @ Zone.Ground.PickupItem =>
zone.Ground forward msg
//frwd to Deployable Actor
case msg @ Zone.Deployable.Build =>
zone.Deployables forward msg
case msg @ Zone.Deployable.Dismiss =>
zone.Deployables forward msg
//frwd to Vehicle Actor
case msg @ Zone.Vehicle.Spawn =>
zone.Transport forward msg
@ -142,17 +149,21 @@ class ZoneActor(zone : Zone) extends Actor {
val errors = new AtomicInteger(0)
val validateObject : (Int, (PlanetSideGameObject)=>Boolean, String) => Boolean = ValidateObject(guid, slog, errors)
//check base to object associations
map.ObjectToBuilding.foreach({ case((object_guid, building_id)) =>
//check bases
map.ObjectToBuilding.values.toSet[Int].foreach(building_id =>
if(zone.Building(building_id).isEmpty) {
slog.error(s"expected a building at id #$building_id")
slog.error(s"expected a building for id #$building_id")
errors.incrementAndGet()
}
)
//check base to object associations
map.ObjectToBuilding.keys.foreach(object_guid =>
if(guid(object_guid).isEmpty) {
slog.error(s"expected object id $object_guid to exist, but it did not")
errors.incrementAndGet()
}
})
)
//check door to lock association
map.DoorToLock.foreach({ case((door_guid, lock_guid)) =>
@ -174,8 +185,8 @@ class ZoneActor(zone : Zone) extends Actor {
//check manned turret to weapon association
map.TurretToWeapon.foreach({ case ((turret_guid, weapon_guid)) =>
validateObject(turret_guid, MannedTurretCheck, "manned turret mount")
if(validateObject(weapon_guid, WeaponCheck, "manned turret weapon")) {
validateObject(turret_guid, FacilityTurretCheck, "facility turret mount")
if(validateObject(weapon_guid, WeaponCheck, "facility turret weapon")) {
if(guid(weapon_guid).get.asInstanceOf[Tool].AmmoSlots.count(!_.Box.HasGUID) > 0) {
slog.error(s"expected weapon $weapon_guid has an unregistered ammunition unit")
errors.incrementAndGet()
@ -247,9 +258,9 @@ object ZoneActor {
obj.isInstanceOf[VehicleSpawnPad]
}
def MannedTurretCheck(obj : PlanetSideGameObject) : Boolean = {
import net.psforever.objects.serverobject.turret.MannedTurret
obj.isInstanceOf[MannedTurret]
def FacilityTurretCheck(obj : PlanetSideGameObject) : Boolean = {
import net.psforever.objects.serverobject.turret.FacilityTurret
obj.isInstanceOf[FacilityTurret]
}
def WeaponCheck(obj : PlanetSideGameObject) : Boolean = {

View file

@ -0,0 +1,204 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.zones
import akka.actor.Actor
import net.psforever.objects.ce.Deployable
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.PlanetSideGameObject
import scala.annotation.tailrec
import scala.collection.mutable.ListBuffer
/**
* na
* @param zone the `Zone` object
*/
class ZoneDeployableActor(zone : Zone, deployableList : ListBuffer[PlanetSideGameObject with Deployable]) extends Actor {
import ZoneDeployableActor._
def receive : Receive = {
case Zone.Deployable.Build(obj, tool) =>
if(DeployableBuild(obj, deployableList)) {
obj match {
case o : PlanetSideServerObject =>
obj.Definition.Initialize(o, context)
case _ =>
obj.Definition.Initialize(obj, context)
}
sender ! Zone.Deployable.DeployableIsBuilt(obj, tool)
}
case Zone.Deployable.Dismiss(obj) =>
if(DeployableDismiss(obj, deployableList)) {
obj match {
case o : PlanetSideServerObject =>
obj.Definition.Uninitialize(o, context)
case _ =>
obj.Definition.Uninitialize(obj, context)
}
sender ! Zone.Deployable.DeployableIsDismissed(obj)
}
case _ => ;
}
}
object ZoneDeployableActor {
def DeployableBuild(obj : PlanetSideGameObject with Deployable, deployableList : ListBuffer[PlanetSideGameObject with Deployable]) : Boolean = {
deployableList.find(d => d == obj) match {
case Some(_) =>
false
case None =>
deployableList += obj
true
}
}
def DeployableDismiss(obj : PlanetSideGameObject with Deployable, deployableList : ListBuffer[PlanetSideGameObject with Deployable]) : Boolean = {
recursiveFindDeployable(deployableList.iterator, obj) match {
case None =>
false
case Some(index) =>
deployableList.remove(index)
true
}
}
@tailrec final def recursiveFindDeployable(iter : Iterator[PlanetSideGameObject with Deployable], target : PlanetSideGameObject with Deployable, index : Int = 0) : Option[Int] = {
if(!iter.hasNext) {
None
}
else {
if(iter.next == target) {
Some(index)
}
else {
recursiveFindDeployable(iter, target, index + 1)
}
}
}
// /**
// * Add an `avatar` as the key of an `Avatar` to `Player` object pair in the given collection.
// * @param avatar an `Avatar` object
// * @param playerMap the mapping of `Avatar` objects to `Player` objects
// * @return true, if the mapping is for a new key;
// * false, if the key already exists
// */
// def PopulationJoin(avatar : Avatar, playerMap : TrieMap[Avatar, Option[Player]]) : Boolean = {
// playerMap.get(avatar) match {
// case Some(_) =>
// false
// case None =>
// playerMap += avatar -> None
// true
// }
// }
// /**
// * Remove an `avatar` from the key of an `Avatar` to `Player` object pair in the given collection.
// * If a `Player` object is associated at the time, return it safely.
// * @param avatar an `Avatar` object
// * @param playerMap the mapping of `Avatar` objects to `Player` objects
// * @return any `Player` object that was associated at the time the `avatar` was removed
// */
// def PopulationLeave(avatar : Avatar, playerMap : TrieMap[Avatar, Option[Player]]) : Option[Player] = {
// playerMap.remove(avatar) match {
// case None =>
// None
// case Some(tplayer) =>
// tplayer
// }
// }
//
// /**
// * Associate a `Player` object as a value to an existing `Avatar` object that will be its key.
// * Do not overwrite players that are already associated.
// * @param avatar an `Avatar` object
// * @param player a `Player` object
// * @param playerMap the mapping of `Avatar` objects to `Player` objects
// * @return the `Player` object that is associated with the `Avatar` key
// */
// def PopulationSpawn(avatar : Avatar, player : Player, playerMap : TrieMap[Avatar, Option[Player]]) : Option[Player] = {
// playerMap.get(avatar) match {
// case None =>
// None
// case Some(tplayer) =>
// tplayer match {
// case Some(aplayer) =>
// Some(aplayer)
// case None =>
// playerMap(avatar) = Some(player)
// Some(player)
// }
// }
// }
//
// /**
// * Disassociate a `Player` object from an existing `Avatar` object that was be its key.
// * @param avatar an `Avatar` object
// * @param playerMap the mapping of `Avatar` objects to `Player` objects
// * @return any `Player` object that is associated at the time
// */
// def PopulationRelease(avatar : Avatar, playerMap : TrieMap[Avatar, Option[Player]]) : Option[Player] = {
// playerMap.get(avatar) match {
// case None =>
// None
// case Some(tplayer) =>
// playerMap(avatar) = None
// tplayer
// }
// }
//
// /**
// * If the given `player` passes a condition check, add it to the list.
// * @param player a `Player` object
// * @param corpseList a list of `Player` objects
// * @return true, if the `player` was added to the list;
// * false, otherwise
// */
// def CorpseAdd(player : Player, corpseList : ListBuffer[Player]) : Boolean = {
// if(player.isBackpack) {
// corpseList += player
// true
// }
// else {
// false
// }
// }
//
// /**
// * Remove the given `player` from the list.
// * @param player a `Player` object
// * @param corpseList a list of `Player` objects
// */
// def CorpseRemove(player : Player, corpseList : ListBuffer[Player]) : Unit = {
// recursiveFindCorpse(corpseList.iterator, player) match {
// case None => ;
// case Some(index) =>
// corpseList.remove(index)
// }
// }
//
// /**
// * A recursive function that finds and removes a specific player from a list of players.
// * @param iter an `Iterator` of `Player` objects
// * @param player the target `Player`
// * @param index the index of the discovered `Player` object
// * @return the index of the `Player` object in the list to be removed;
// * `None`, otherwise
// */
// @tailrec final def recursiveFindCorpse(iter : Iterator[Player], player : Player, index : Int = 0) : Option[Int] = {
// if(!iter.hasNext) {
// None
// }
// else {
// if(iter.next == player) {
// Some(index)
// }
// else {
// recursiveFindCorpse(iter, player, index + 1)
// }
// }
// }
}

View file

@ -2,9 +2,10 @@
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import net.psforever.types.Vector3
import net.psforever.types.{Angular, Vector3}
import scodec.Codec
import scodec.codecs._
import shapeless.{::, HNil}
/**
* Dispatched from the client to request that an object be deployed.<br>
@ -15,17 +16,13 @@ import scodec.codecs._
* @param object_guid the object
* @param unk1 na
* @param pos the location where the object is to be deployed
* @param roll the amount of roll that affects orientation
* @param pitch the amount of pitch that affects orientation
* @param yaw the amount of yaw that affects orientation
* @param orient the angle of orientation
* @param unk2 na
*/
final case class DeployObjectMessage(object_guid : PlanetSideGUID,
unk1 : Long,
pos : Vector3,
roll : Int,
pitch : Int,
yaw : Int,
orient : Vector3,
unk2 : Long)
extends PlanetSideGamePacket {
type Packet = DeployObjectMessage
@ -38,9 +35,19 @@ object DeployObjectMessage extends Marshallable[DeployObjectMessage] {
("object_guid" | PlanetSideGUID.codec) ::
("unk1" | uint32L) ::
("pos" | Vector3.codec_pos) ::
("roll" | uint8L) ::
("pitch" | uint8L) ::
("yaw" | uint8L) ::
(("roll" | Angular.codec_roll) ::
("pitch" | Angular.codec_pitch) ::
("yaw" | Angular.codec_yaw())
).xmap[Vector3] (
{
case x :: y :: z :: HNil =>
Vector3(x, y, z)
},
{
case Vector3(x, y, z) =>
x :: y :: z :: HNil
}
) ::
("unk2" | uint32L)
).as[DeployObjectMessage]
}

View file

@ -9,7 +9,7 @@ import shapeless.{::, HNil}
/**
* An `Enumeration` for the forms of the event chat message produced by this packet.
*/
object DeploymentOutcome extends Enumeration(1) {
object DeployOutcome extends Enumeration(1) {
type Type = Value
val Failure = Value(2)
@ -47,7 +47,7 @@ object DeploymentOutcome extends Enumeration(1) {
*/
final case class ObjectDeployedMessage(unk : Int,
desc : String,
action : DeploymentOutcome.Value,
action : DeployOutcome.Value,
count : Long,
max : Long)
extends PlanetSideGamePacket {
@ -65,13 +65,31 @@ object ObjectDeployedMessage extends Marshallable[ObjectDeployedMessage] {
* @param max the maximum number of this type of object that can be deployed
* @return an `ObjectDeployedMessage` object
*/
def apply(desc : String, action : DeploymentOutcome.Value, count : Long, max : Long) : ObjectDeployedMessage =
def apply(desc : String, action : DeployOutcome.Value, count : Long, max : Long) : ObjectDeployedMessage =
new ObjectDeployedMessage(0, desc, action, count, max)
/**
* na
* @param desc descriptive text of what kind of object is being deployed
* @param count the number of this type of object deployed
* @param max the maximum number of this type of object that can be deployed
* @return an `ObjectDeployedMessage` object
*/
def Success(desc : String, count : Int, max : Int) : ObjectDeployedMessage =
new ObjectDeployedMessage(0, desc, DeployOutcome.Success, count, max)
/**
* na
* @param desc descriptive text of what kind of object failed to be deployed
* @return an `ObjectDeployedMessage` object
*/
def Failure(desc : String) : ObjectDeployedMessage =
new ObjectDeployedMessage(0, desc, DeployOutcome.Failure, 0, 0)
implicit val codec : Codec[ObjectDeployedMessage] = (
("unk" | uint16L) ::
("desc" | PacketHelpers.encodedString) ::
("action" | DeploymentOutcome.codec) ::
("action" | DeployOutcome.codec) ::
("count" | uint32L) ::
("max" | uint32L)
).xmap[ObjectDeployedMessage] (

View file

@ -13,7 +13,8 @@ import scodec.codecs._
* `67 - ???`<br>
* <br>
* Global (GUID=0)<br>
* `82 - ???`
* `75 - Russian client region check` (value checks with bitmask `& 8`)<br>
* `82 - ???`<br>
* `83 - max boomers`<br>
* `84 - max he mines`<br>
* `85 - max disruptor mines`<br>
@ -114,10 +115,10 @@ import scodec.codecs._
* `36 - CR. Value is the CR`<br>
* `43 - Info on avatar name : 0 = Nothing, 1 = "(LD)" message`<br>
* `45 - NTU charge bar 0-10, 5 = 50% full. Seems to apply to both ANT and NTU Silo (possibly siphons?)`<br>
* 47 - Sets base NTU level to CRITICAL. MUST use base modelId not base GUID
* 48 - Set to 1 to send base power loss message & turns on red warning lights throughout base. MUST use base modelId not base GUID
* 49 - Vehicle texture effects state? (>0 turns on ANT panel glow or ntu silo panel glow + orbs) (bit?)
* `52 - Vehicle particle effects? (>0 turns on orbs going towards ANT. Doesn't affect silo) (bit?)
* `47 - Sets base NTU level to CRITICAL. MUST use base modelId not base GUID`<br>
* `48 - Set to 1 to send base power loss message & turns on red warning lights throughout base. MUST use base modelId not base GUID`<br>
* `49 - Vehicle texture effects state? (>0 turns on ANT panel glow or ntu silo panel glow + orbs) (bit?)`<br>
* `52 - Vehicle particle effects? (>0 turns on orbs going towards ANT. Doesn't affect silo) (bit?)`<br>
* `53 - LFS. Value is 1 to flag LFS`<br>
* `54 - Player "Aura". Values can be expressed in the first byte's lower nibble:`<br>
* - 0 is nothing<br>
@ -151,7 +152,7 @@ import scodec.codecs._
* `79 - ???`<br>
* `80 - Damage vehicle (unknown value)`<br>
* `81 - ???`<br>
* `113 - `Vehicle capacitor - e.g. Leviathan EMP charge`
* `113 - Vehicle capacitor - e.g. Leviathan EMP charge`
*
* @param player_guid the player
* @param attribute_type na

View file

@ -2,9 +2,10 @@
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import net.psforever.types.Vector3
import net.psforever.types.{Angular, Vector3}
import scodec.Codec
import scodec.codecs._
import shapeless.{::, HNil}
/**
* na
@ -19,14 +20,11 @@ final case class TriggeredEffect(unk1 : Boolean,
* Activate an effect that is not directly associated with an existing game object.
* Without a game object from which to inherit position and orientation, those explicit parameters must be provided.
* @param pos the position in the game world
* @param roll the amount of roll that affects orientation
* @param pitch the amount of pitch that affects orientation
* @param yaw the amount of yaw that affects orientation
* @param orient the angle of orientation
*/
//TODO must find proper North-corrective angle for orientation
final case class TriggeredEffectLocation(pos : Vector3,
roll : Int,
pitch : Int,
yaw : Int)
orient : Vector3)
/**
* Dispatched by the server to cause a client to display a special graphical effect.<br>
@ -38,14 +36,14 @@ final case class TriggeredEffectLocation(pos : Vector3,
* For example, the effect "on" will only work on objects that accept "on" normally, like a deployed `motionalarmsensor`.
* The effect "spawn_object_effect" can be applied anywhere in the environment;
* but, it can not be activated in conjunction with an existing object.
* @param obj an object that accepts the effect
* @param object_guid an object that accepts the effect
* @param effect the name of the effect
* @param unk na;
* when activating an effect on an existing object
* @param location an optional position where the effect will be displayed;
* when activating an effect independently
*/
final case class TriggerEffectMessage(obj : PlanetSideGUID,
final case class TriggerEffectMessage(object_guid : PlanetSideGUID,
effect : String,
unk : Option[TriggeredEffect] = None,
location : Option[TriggeredEffectLocation] = None
@ -56,6 +54,15 @@ final case class TriggerEffectMessage(obj : PlanetSideGUID,
}
object TriggerEffectMessage extends Marshallable[TriggerEffectMessage] {
def apply(object_guid : PlanetSideGUID, effect : String) : TriggerEffectMessage =
TriggerEffectMessage(object_guid, effect, None, None)
def apply(object_guid : PlanetSideGUID, effect : String, unk1 : Boolean, unk2 : Long) : TriggerEffectMessage =
TriggerEffectMessage(object_guid, effect, Some(TriggeredEffect(unk1, unk2)), None)
def apply(effect : String, position : Vector3, orientation : Vector3) : TriggerEffectMessage =
TriggerEffectMessage(PlanetSideGUID(0), effect, None, Some(TriggeredEffectLocation(position, orientation)))
/**
* A `Codec` for `TriggeredEffect` data.
*/
@ -69,15 +76,24 @@ object TriggerEffectMessage extends Marshallable[TriggerEffectMessage] {
*/
private val effect_location_codec : Codec[TriggeredEffectLocation] = (
("pos" | Vector3.codec_pos) ::
("roll" | uint8L) ::
("pitch" | uint8L) ::
("yaw" | uint8L)
(("roll" | Angular.codec_roll) ::
("pitch" | Angular.codec_pitch) ::
("yaw" | Angular.codec_yaw())).xmap[Vector3] (
{
case x :: y :: z :: HNil =>
Vector3(x, y, z)
},
{
case Vector3(x, y, z) =>
x :: y :: z :: HNil
}
)
).as[TriggeredEffectLocation]
implicit val codec : Codec[TriggerEffectMessage] = (
("obj" | PlanetSideGUID.codec) >>:~ { obj =>
("object_guid" | PlanetSideGUID.codec) >>:~ { guid =>
("effect" | PacketHelpers.encodedString) ::
optional(bool, "unk" | effect_codec) ::
conditional(obj.guid == 0, "location" | effect_location_codec)
conditional(guid.guid == 0, "location" | effect_location_codec)
}).as[TriggerEffectMessage]
}

View file

@ -0,0 +1,71 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
import scodec.codecs._
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
*/
final case class LargeDeployableData(deploy : SmallDeployableData,
health : Int,
internals : Option[InventoryData] = None
) extends ConstructorData {
override def bitsize : Long = {
val deploySize = deploy.bitsize
val internalSize = internals match {
case Some(inv) =>
inv.bitsize
case None =>
0
}
22L + deploySize + internalSize //8u + 7u + 4u + 2u + 1u
}
}
object LargeDeployableData extends Marshallable[LargeDeployableData] {
implicit val codec : Codec[LargeDeployableData] = (
("deploy" | SmallDeployableData.codec) ::
("health" | uint8L) ::
uintL(7) ::
uint4L ::
uint2L ::
optional(bool, "internals" | InventoryData.codec)
).exmap[LargeDeployableData] (
{
case deploy :: health :: 0 :: 0xF :: 0 :: internals :: HNil =>
val (newHealth, newInternals) = if(health == 0 || internals.isEmpty || internals.get.contents.isEmpty) {
(0, None)
}
else {
(health, internals)
}
Attempt.successful(LargeDeployableData(deploy, newHealth, newInternals))
case _ =>
Attempt.failure(Err("invalid large deployable data format"))
},
{
case LargeDeployableData(deploy, health, internals) =>
val (newHealth, newInternals) = if(health == 0 || internals.isEmpty || internals.get.contents.isEmpty) {
(0, None)
}
else {
(health, internals)
}
Attempt.successful(deploy :: newHealth :: 0 :: 0xF :: 0 :: newInternals :: HNil)
}
)
}

View file

@ -955,7 +955,7 @@ object ObjectClass {
case ObjectClass.trek => ConstructorData.genericCodec(WeaponData.codec, "tool")
//ace deployables
case ObjectClass.ace => ConstructorData.genericCodec(ACEData.codec, "ace")
case ObjectClass.advanced_ace => ConstructorData.genericCodec(CommandDetonaterData.codec, "advanced ace")
case ObjectClass.advanced_ace => ConstructorData.genericCodec(ACEData.codec, "advanced ace")
case ObjectClass.boomer_trigger => ConstructorData.genericCodec(BoomerTriggerData.codec, "boomer trigger")
//vehicles?
case ObjectClass.orbital_shuttle => ConstructorData.genericCodec(OrbitalShuttleData.codec, "HART")
@ -1186,7 +1186,7 @@ object ObjectClass {
case ObjectClass.trek => DroppedItemData.genericCodec(WeaponData.codec, "tool")
//ace deployables
case ObjectClass.ace => DroppedItemData.genericCodec(ACEData.codec, "ace")
case ObjectClass.advanced_ace => DroppedItemData.genericCodec(CommandDetonaterData.codec, "advanced ace") //todo temporary?
case ObjectClass.advanced_ace => DroppedItemData.genericCodec(ACEData.codec, "advanced ace") //todo temporary?
case ObjectClass.boomer_trigger => DroppedItemData.genericCodec(BoomerTriggerData.codec, "boomer trigger")
case ObjectClass.boomer => ConstructorData.genericCodec(SmallDeployableData.codec, "ace deployable")
case ObjectClass.he_mine => ConstructorData.genericCodec(SmallDeployableData.codec, "ace deployable")

View file

@ -97,7 +97,7 @@ object ObjectCreateBase {
}
catch {
case ex : Exception =>
log.error(s"${ex.getClass.toString} - ${ex.toString}")
log.error(s"${ex.getClass.toString} - ${ex.toString} ($objectClass)")
}
out
}
@ -125,7 +125,7 @@ object ObjectCreateBase {
}
catch {
case ex : Exception =>
log.error(s"${ex.getClass.toString} - ${ex.toString}")
log.error(s"${ex.getClass.toString} - ${ex.toString} ($objectClass)")
}
out
}

View file

@ -21,14 +21,19 @@ import shapeless.{::, HNil}
* @param health the amount of health the object has, as a percentage of a filled bar
* @param internals data regarding the mountable weapon
*/
final case class OneMannedFieldTurretData(deploy : CommonFieldData,
final case class OneMannedFieldTurretData(deploy : SmallDeployableData,
health : Int,
internals : Option[InternalSlot] = None
internals : Option[InventoryData] = None
) extends ConstructorData {
override def bitsize : Long = {
val deploySize = deploy.bitsize
val internalSize = if(internals.isDefined) { CommonFieldData.internalWeapon_bitsize + internals.get.bitsize } else { 0L }
38L + deploySize + internalSize //16u + 8u + 8u + 2u + 4u
val internalSize = internals match {
case Some(inv) =>
inv.bitsize
case None =>
0
}
37L + deploySize + internalSize //16u + 1u + 8u + 5u + 4u + 2u + 1u
}
}
@ -40,7 +45,7 @@ object OneMannedFieldTurretData extends Marshallable[OneMannedFieldTurretData] {
* @param internals data regarding the mountable weapon
* @return a `OneMannedFieldTurretData` object
*/
def apply(deploy : CommonFieldData, health : Int, internals : InternalSlot) : OneMannedFieldTurretData =
def apply(deploy : SmallDeployableData, health : Int, internals : InventoryData) : OneMannedFieldTurretData =
new OneMannedFieldTurretData(deploy, health, Some(internals))
/**
@ -122,40 +127,44 @@ object OneMannedFieldTurretData extends Marshallable[OneMannedFieldTurretData] {
)
implicit val codec : Codec[OneMannedFieldTurretData] = (
("deploy" | CommonFieldData.codec) ::
bool ::
PlanetSideGUID.codec :: //hoist/extract with the CommonFieldData above
("deploy" | SmallDeployableData.codec) ::
PlanetSideGUID.codec :: //hoist/extract with the deploy.owner_guid in field above
bool ::
("health" | uint8L) ::
uint2L ::
uint8L ::
bool ::
optional(bool, "internals" | CommonFieldData.internalWeaponCodec)
uint(5) ::
uint4 ::
uint2 ::
optional(bool, "internals" | InventoryData.codec)
).exmap[OneMannedFieldTurretData] (
{
case deploy :: false :: player :: false :: health :: 0 :: 0x1E :: false :: internals :: HNil =>
var newHealth : Int = health
var newInternals : Option[InternalSlot] = internals
if(health == 0 || internals.isEmpty) {
newHealth = 0
newInternals = None
case deploy :: player :: false :: health :: 0 :: 0xF :: 0 :: internals :: HNil =>
val (newHealth, newInternals) = if(health == 0 || internals.isEmpty || internals.get.contents.isEmpty) {
(0, None)
}
val newDeploy = CommonFieldData(deploy.pos, deploy.faction, deploy.unk, player)
Attempt.successful(OneMannedFieldTurretData(newDeploy, newHealth, newInternals))
else {
(health, internals)
}
Attempt.successful(
OneMannedFieldTurretData(
SmallDeployableData(deploy.pos, deploy.faction, deploy.bops, deploy.destroyed, deploy.unk1, deploy.jammered, deploy.unk2, player),
newHealth,
newInternals
)
)
case _ =>
Attempt.failure(Err("invalid omft data format"))
},
{
case OneMannedFieldTurretData(deploy, health, internals) =>
var newHealth : Int = health
var newInternals : Option[InternalSlot] = internals
if(health == 0 || internals.isEmpty) {
newHealth = 0
newInternals = None
val (newHealth, newInternals) = if(health == 0 || internals.isEmpty || internals.get.contents.isEmpty) {
(0, None)
}
val newDeploy = CommonFieldData(deploy.pos, deploy.faction, deploy.unk)
Attempt.successful(newDeploy :: false :: deploy.player_guid :: false :: newHealth :: 0 :: 0x1E :: false :: newInternals :: HNil)
else {
(health, internals)
}
val newDeploy = SmallDeployableData(deploy.pos, deploy.faction, deploy.bops, deploy.destroyed, deploy.unk1, deploy.jammered, deploy.unk2, PlanetSideGUID(0))
Attempt.successful(newDeploy :: deploy.owner_guid :: false :: newHealth :: 0 :: 0xF :: 0 :: newInternals :: HNil)
case _ =>
Attempt.failure(Err("invalid omft data format"))

View file

@ -2,33 +2,42 @@
package net.psforever.packet.game.objectcreate
import net.psforever.packet.Marshallable
import scodec.{Attempt, Codec, Err}
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.PlanetSideEmpire
import scodec.Codec
import scodec.codecs._
import shapeless.{::, HNil}
/**
* A representation of simple objects that are spawned by the adaptive construction engine.
* @param deploy data common to objects spawned by the (advanced) adaptive construction engine
* //@param deploy data common to objects spawned by the (advanced) adaptive construction engine
*/
final case class SmallDeployableData(deploy : CommonFieldData) extends ConstructorData {
override def bitsize : Long = deploy.bitsize + 1L
final case class SmallDeployableData(pos : PlacementData,
faction : PlanetSideEmpire.Value,
bops : Boolean,
destroyed : Boolean,
unk1 : Int,
jammered : Boolean,
unk2 : Boolean,
owner_guid : PlanetSideGUID) extends ConstructorData {
override def bitsize : Long = {
val posSize = pos.bitsize
24 + posSize
}
}
object SmallDeployableData extends Marshallable[SmallDeployableData] {
implicit val codec : Codec[SmallDeployableData] = (
("deploy" | CommonFieldData.codec) ::
bool
).exmap[SmallDeployableData] (
{
case deploy :: false :: HNil =>
Attempt.successful(SmallDeployableData(deploy))
def apply(pos : PlacementData, faction : PlanetSideEmpire.Value, unk1 : Int, jammered : Boolean, unk2 : Boolean) : SmallDeployableData = {
SmallDeployableData(pos, faction, false, false, unk1, jammered, unk2, PlanetSideGUID(0))
}
case _ =>
Attempt.failure(Err("invalid small deployable data format"))
},
{
case SmallDeployableData(deploy) =>
Attempt.successful(deploy :: false :: HNil)
}
)
implicit val codec : Codec[SmallDeployableData] = (
("pos" | PlacementData.codec) ::
("faction" | PlanetSideEmpire.codec) ::
("bops" | bool) ::
("destroyed" | bool) ::
("unk1" | uint2L) :: //3 - na, 2 - common, 1 - na, 0 - common?
("jammered" | bool) ::
("unk2" | bool) ::
("owner_guid" | PlanetSideGUID.codec)
).as[SmallDeployableData]
}

View file

@ -20,14 +20,19 @@ import shapeless.{::, HNil}
* @param health the amount of health the object has, as a percentage of a filled bar
* @param internals data regarding the mounted weapon
*/
final case class SmallTurretData(deploy : CommonFieldData,
final case class SmallTurretData(deploy : SmallDeployableData,
health : Int,
internals : Option[InternalSlot] = None
internals : Option[InventoryData] = None
) extends ConstructorData {
override def bitsize : Long = {
val deploySize = deploy.bitsize
val internalSize = if(internals.isDefined) { CommonFieldData.internalWeapon_bitsize + internals.get.bitsize } else { 0L }
23L + deploySize + internalSize //1u + 8u + 7u + 4u + 2u + 1u
val internalSize = internals match {
case Some(inv) =>
inv.bitsize
case None =>
0
}
22L + deploySize + internalSize //8u + 7u + 4u + 2u + 1u
}
}
@ -39,7 +44,7 @@ object SmallTurretData extends Marshallable[SmallTurretData] {
* @param internals data regarding the mounted weapon
* @return a `SmallTurretData` object
*/
def apply(deploy : CommonFieldData, health : Int, internals : InternalSlot) : SmallTurretData =
def apply(deploy : SmallDeployableData, health : Int, internals : InventoryData) : SmallTurretData =
new SmallTurretData(deploy, health, Some(internals))
/**
@ -81,21 +86,20 @@ object SmallTurretData extends Marshallable[SmallTurretData] {
)
implicit val codec : Codec[SmallTurretData] = (
("deploy" | CommonFieldData.codec) ::
bool ::
("deploy" | SmallDeployableData.codec) ::
("health" | uint8L) ::
uintL(7) ::
uint4L ::
uint2L ::
optional(bool, "internals" | CommonFieldData.internalWeaponCodec)
optional(bool, "internals" | InventoryData.codec)
).exmap[SmallTurretData] (
{
case deploy :: false :: health :: 0 :: 0xF :: 0 :: internals :: HNil =>
var newHealth : Int = health
var newInternals : Option[InternalSlot] = internals
if(health == 0 || internals.isEmpty) {
newHealth = 0
newInternals = None
case deploy :: health :: 0 :: 0xF :: 0 :: internals :: HNil =>
val (newHealth, newInternals) = if(health == 0 || internals.isEmpty || internals.get.contents.isEmpty) {
(0, None)
}
else {
(health, internals)
}
Attempt.successful(SmallTurretData(deploy, newHealth, newInternals))
@ -104,13 +108,13 @@ object SmallTurretData extends Marshallable[SmallTurretData] {
},
{
case SmallTurretData(deploy, health, internals) =>
var newHealth : Int = health
var newInternals : Option[InternalSlot] = internals
if(health == 0 || internals.isEmpty) {
newHealth = 0
newInternals = None
val (newHealth, newInternals) = if(health == 0 || internals.isEmpty || internals.get.contents.isEmpty) {
(0, None)
}
Attempt.successful(deploy :: false :: newHealth :: 0 :: 0xF :: 0 :: newInternals :: HNil)
else {
(health, internals)
}
Attempt.successful(deploy :: newHealth :: 0 :: 0xF :: 0 :: newInternals :: HNil)
}
)
}

View file

@ -12,25 +12,24 @@ 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 TRAPData(deploy : CommonFieldData,
final case class TRAPData(deploy : SmallDeployableData,
health : Int
) extends ConstructorData {
override def bitsize : Long = {
23L + deploy.bitsize //8u + 7u + 4u + 3u + 1u
22L + deploy.bitsize //8u + 7u + 4u + 3u
}
}
object TRAPData extends Marshallable[TRAPData] {
implicit val codec : Codec[TRAPData] = (
("deploy" | CommonFieldData.codec) ::
bool ::
("deploy" | SmallDeployableData.codec) ::
("health" | uint8L) ::
uint(7) ::
uint4L ::
uint(3)
).exmap[TRAPData] (
{
case deploy :: false :: health :: 0 :: 15 :: 0 :: HNil =>
case deploy :: health :: 0 :: 15 :: 0 :: HNil =>
Attempt.successful(TRAPData(deploy, health))
case _ =>
@ -38,7 +37,7 @@ object TRAPData extends Marshallable[TRAPData] {
},
{
case TRAPData(deploy, health) =>
Attempt.successful(deploy :: false :: health :: 0 :: 15 :: 0 :: HNil)
Attempt.successful(deploy :: health :: 0 :: 15 :: 0 :: HNil)
}
)
}

View file

@ -69,6 +69,13 @@ object Vector3 {
("z" | floatL)
).as[Vector3]
/**
* A common vector object that only concerns itself with rotation around the world-up axis.
* @param yaw the angle of rotation
* @return a `Vector3` object
*/
def z(yaw : Float) : Vector3 = Vector3(0, 0, yaw)
/**
* Calculate the actual distance between two points.
* @param pos1 the first point

View file

@ -48,7 +48,7 @@ abstract class RemoverActor extends SupportActor[RemoverActor.Entry] {
*/
var secondHeap : List[RemoverActor.Entry] = List()
private var taskResolver : ActorRef = Actor.noSender
protected var taskResolver : ActorRef = Actor.noSender
val sameEntryComparator = new SimilarityComparator[RemoverActor.Entry]() {
def Test(entry1 : RemoverActor.Entry, entry2 : RemoverActor.Entry) : Boolean = {
@ -101,7 +101,16 @@ abstract class RemoverActor extends SupportActor[RemoverActor.Entry] {
val entry = RemoverActor.Entry(obj, zone, duration.getOrElse(FirstStandardDuration).toNanos)
if(InclusionTest(entry) && !secondHeap.exists(test => sameEntryComparator.Test(test, entry) )) {
InitialJob(entry)
if(firstHeap.isEmpty) {
if(entry.duration == 0) {
//skip the first queue altogether
FirstJob(entry)
secondHeap = secondHeap ++ List(RepackageEntry(entry))
if(secondHeap.size == 1) {
import scala.concurrent.ExecutionContext.Implicits.global
secondTask = context.system.scheduler.scheduleOnce(SecondStandardDuration, self, RemoverActor.TryDelete())
}
}
else if(firstHeap.isEmpty) {
//we were the only entry so the event must be started from scratch
firstHeap = List(entry)
trace(s"a remover task has been added: $entry")

View file

@ -2,6 +2,7 @@
package services.avatar
import net.psforever.objects.ballistics.SourceEntry
import net.psforever.objects.ce.Deployable
import net.psforever.objects.{PlanetSideGameObject, Player}
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.inventory.Container
@ -9,7 +10,7 @@ import net.psforever.objects.zones.Zone
import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.{PlanetSideGUID, PlayerStateMessageUpstream}
import net.psforever.packet.game.objectcreate.{ConstructorData, ObjectCreateMessageParent}
import net.psforever.types.{ExoSuitType, Vector3}
import net.psforever.types.{ExoSuitType, PlanetSideEmpire, Vector3}
import scala.concurrent.duration.FiniteDuration
@ -23,20 +24,23 @@ object AvatarAction {
final case class ChangeFireState_Stop(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action
final case class ConcealPlayer(player_guid : PlanetSideGUID) extends Action
final case class Damage(player_guid : PlanetSideGUID, target : Player, resolution_function : (Any)=>Unit) extends Action
final case class DeployItem(player_guid : PlanetSideGUID, item : PlanetSideGameObject with Deployable) extends Action
final case class Destroy(victim : PlanetSideGUID, killer : PlanetSideGUID, weapon : PlanetSideGUID, pos : Vector3) extends Action
final case class DestroyDisplay(killer : SourceEntry, victim : SourceEntry, method : Int, unk : Int = 121) extends Action
final case class DropItem(player_guid : PlanetSideGUID, item : Equipment, zone : Zone) extends Action
final case class EquipmentInHand(player_guid : PlanetSideGUID, target_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action
final case class HitHint(source_guid : PlanetSideGUID, player_guid : PlanetSideGUID) extends Action
final case class LoadPlayer(player_guid : PlanetSideGUID, object_id : Int, target_guid : PlanetSideGUID, cdata : ConstructorData, pdata : Option[ObjectCreateMessageParent]) extends Action
final case class KilledWhileInVehicle(player_guid : PlanetSideGUID) extends Action
final case class LoadPlayer(player_guid : PlanetSideGUID, object_id : Int, target_guid : PlanetSideGUID, cdata : ConstructorData, pdata : Option[ObjectCreateMessageParent]) extends Action
final case class ObjectDelete(player_guid : PlanetSideGUID, item_guid : PlanetSideGUID, unk : Int = 0) extends Action
final case class ObjectHeld(player_guid : PlanetSideGUID, slot : Int) extends Action
final case class PlanetsideAttribute(player_guid : PlanetSideGUID, attribute_type : Int, attribute_value : Long) extends Action
final case class PlayerState(player_guid : PlanetSideGUID, msg : PlayerStateMessageUpstream, spectator : Boolean, weaponInHand : Boolean) extends Action
final case class PickupItem(player_guid : PlanetSideGUID, zone : Zone, target : PlanetSideGameObject with Container, slot : Int, item : Equipment, unk : Int = 0) extends Action
final case class PutDownFDU(player_guid : PlanetSideGUID) extends Action
final case class Release(player : Player, zone : Zone, time : Option[FiniteDuration] = None) extends Action
final case class Reload(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action
final case class SetEmpire(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, faction : PlanetSideEmpire.Value) extends Action
final case class StowEquipment(player_guid : PlanetSideGUID, target_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action
final case class WeaponDryFire(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action

View file

@ -5,9 +5,9 @@ import net.psforever.objects.Player
import net.psforever.objects.ballistics.SourceEntry
import net.psforever.objects.equipment.Equipment
import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.{ObjectCreateMessage, PlanetSideGUID, PlayerStateMessageUpstream}
import net.psforever.packet.game._
import net.psforever.packet.game.objectcreate.ConstructorData
import net.psforever.types.{ExoSuitType, Vector3}
import net.psforever.types.{ExoSuitType, PlanetSideEmpire, Vector3}
object AvatarResponse {
trait Response
@ -30,8 +30,10 @@ object AvatarResponse {
final case class ObjectHeld(slot : Int) extends Response
final case class PlanetsideAttribute(attribute_type : Int, attribute_value : Long) extends Response
final case class PlayerState(msg : PlayerStateMessageUpstream, spectator : Boolean, weaponInHand : Boolean) extends Response
final case class PutDownFDU(target_guid : PlanetSideGUID) extends Response
final case class Release(player : Player) extends Response
final case class Reload(weapon_guid : PlanetSideGUID) extends Response
final case class SetEmpire(object_guid : PlanetSideGUID, faction : PlanetSideEmpire.Value) extends Response
final case class StowEquipment(target_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Response
final case class WeaponDryFire(weapon_guid : PlanetSideGUID) extends Response

View file

@ -10,7 +10,6 @@ import services.{GenericEventBus, RemoverActor, Service}
class AvatarService extends Actor {
private val undertaker : ActorRef = context.actorOf(Props[CorpseRemovalActor], "corpse-removal-agent")
private val janitor = context.actorOf(Props[DroppedItemRemover], "item-remover-agent")
//undertaker ! "startup"
private [this] val log = org.log4s.getLogger
@ -69,6 +68,14 @@ class AvatarService extends Actor {
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.DamageResolution(target, resolution_function))
)
case AvatarAction.DeployItem(player_guid, item) =>
val definition = item.Definition
val objectData = definition.Packet.ConstructorData(item).get
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid,
AvatarResponse.DropItem(ObjectCreateMessage(definition.ObjectId, item.GUID, objectData))
)
)
case AvatarAction.Destroy(victim, killer, weapon, pos) =>
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", victim, AvatarResponse.Destroy(victim, killer, weapon, pos))
@ -148,6 +155,10 @@ class AvatarService extends Actor {
}
})
)
case AvatarAction.PutDownFDU(player_guid) =>
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.PutDownFDU(player_guid))
)
case AvatarAction.Release(player, zone, time) =>
undertaker forward RemoverActor.AddTask(player, zone, time)
AvatarEvents.publish(
@ -157,6 +168,10 @@ class AvatarService extends Actor {
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.Reload(weapon_guid))
)
case AvatarAction.SetEmpire(player_guid, target_guid, faction) =>
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.SetEmpire(target_guid, faction))
)
case AvatarAction.StowEquipment(player_guid, target_guid, slot, obj) =>
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.StowEquipment(target_guid, slot, obj))
@ -177,6 +192,7 @@ class AvatarService extends Actor {
case AvatarServiceMessage.Corpse(msg) =>
undertaker forward msg
//message to Janitor
case AvatarServiceMessage.Ground(msg) =>
janitor forward msg

View file

@ -21,7 +21,7 @@ class DroppedItemRemover extends RemoverActor {
def FirstJob(entry : RemoverActor.Entry) : Unit = {
import net.psforever.objects.zones.Zone
entry.zone.Ground ! Zone.Ground.PickupItem(entry.obj.GUID)
entry.zone.Ground ! Zone.Ground.RemoveItem(entry.obj.GUID)
context.parent ! AvatarServiceMessage(entry.zone.Id, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, entry.obj.GUID))
}

View file

@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package services.galaxy
import net.psforever.packet.game.{BuildingInfoUpdateMessage}
import net.psforever.packet.game.BuildingInfoUpdateMessage
object GalaxyAction {
trait Action

View file

@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package services.galaxy
import net.psforever.packet.game.{BuildingInfoUpdateMessage}
import net.psforever.packet.game.BuildingInfoUpdateMessage
object GalaxyResponse {
trait Response

View file

@ -1,17 +1,21 @@
// Copyright (c) 2017 PSForever
package services.local
import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.ce.Deployable
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.terminals.CaptureTerminal
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{PlanetSideGUID, TriggeredSound}
import net.psforever.packet.game._
import net.psforever.types.{PlanetSideEmpire, Vector3}
object LocalAction {
trait Action
final case class AlertDestroyDeployable(player_guid : PlanetSideGUID, obj : PlanetSideGameObject with Deployable) extends Action
final case class DeployableMapIcon(player_guid : PlanetSideGUID, behavior : DeploymentAction.Value, deployInfo : DeployableInfo) extends Action
final case class DoorOpens(player_guid : PlanetSideGUID, continent : Zone, door : Door) extends Action
final case class DoorCloses(player_guid : PlanetSideGUID, door_guid : PlanetSideGUID) extends Action
final case class HackClear(player_guid : PlanetSideGUID, target : PlanetSideServerObject, unk1 : Long, unk2 : Long = 8L) extends Action
@ -19,6 +23,9 @@ object LocalAction {
final case class ClearTemporaryHack(player_guid: PlanetSideGUID, target: PlanetSideServerObject with Hackable) extends Action
final case class HackCaptureTerminal(player_guid : PlanetSideGUID, continent : Zone, target : CaptureTerminal, unk1 : Long, unk2 : Long = 8L, isResecured : Boolean) extends Action
final case class ProximityTerminalEffect(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, effectState : Boolean) extends Action
final case class TriggerEffect(player_guid : PlanetSideGUID, effect : String, target : PlanetSideGUID) extends Action
final case class TriggerEffectInfo(player_guid : PlanetSideGUID, effect : String, target : PlanetSideGUID, unk1 : Boolean, unk2 : Long) extends Action
final case class TriggerEffectLocation(player_guid : PlanetSideGUID, effect : String, pos : Vector3, orient : Vector3) extends Action
final case class TriggerSound(player_guid : PlanetSideGUID, sound : TriggeredSound.Value, pos : Vector3, unk : Int, volume : Float) extends Action
final case class SetEmpire(object_guid: PlanetSideGUID, empire: PlanetSideEmpire.Value) extends Action
}

View file

@ -1,18 +1,25 @@
// Copyright (c) 2017 PSForever
package services.local
import net.psforever.packet.game.{PlanetSideGUID, TriggeredSound}
import net.psforever.objects.ce.Deployable
import net.psforever.objects.PlanetSideGameObject
import net.psforever.packet.game._
import net.psforever.types.{PlanetSideEmpire, Vector3}
object LocalResponse {
trait Response
final case class AlertDestroyDeployable(obj : PlanetSideGameObject with Deployable) extends Response
final case class DeployableMapIcon(action : DeploymentAction.Value, deployInfo : DeployableInfo) extends Response
final case class DoorOpens(door_guid : PlanetSideGUID) extends Response
final case class DoorCloses(door_guid : PlanetSideGUID) extends Response
final case class EliminateDeployable(obj : PlanetSideGameObject with Deployable, object_guid : PlanetSideGUID, pos : Vector3) extends Response
final case class HackClear(target_guid : PlanetSideGUID, unk1 : Long, unk2 : Long) extends Response
final case class HackObject(target_guid : PlanetSideGUID, unk1 : Long, unk2 : Long) extends Response
final case class HackCaptureTerminal(target_guid : PlanetSideGUID, unk1 : Long, unk2 : Long, isResecured: Boolean) extends Response
final case class ObjectDelete(item_guid : PlanetSideGUID, unk : Int) extends Response
final case class ProximityTerminalEffect(object_guid : PlanetSideGUID, effectState : Boolean) extends Response
final case class TriggerEffect(target: PlanetSideGUID, effect: String, effectInfo: Option[TriggeredEffect] = None, triggeredLocation: Option[TriggeredEffectLocation] = None) extends Response
final case class TriggerSound(sound : TriggeredSound.Value, pos : Vector3, unk : Int, volume : Float) extends Response
final case class SetEmpire(object_guid: PlanetSideGUID, empire: PlanetSideEmpire.Value) extends Response
}

View file

@ -2,28 +2,30 @@
package services.local
import akka.actor.{Actor, ActorRef, Props}
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.ce.Deployable
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
import net.psforever.objects.serverobject.structures.Building
import net.psforever.objects.serverobject.terminals.CaptureTerminal
import net.psforever.objects.zones.{InterstellarCluster, Zone}
import net.psforever.objects.zones.InterstellarCluster.GetWorld
import services.local.support.{DoorCloseActor, HackCaptureActor, HackClearActor}
import net.psforever.objects.{BoomerDeployable, GlobalDefinitions, PlanetSideGameObject, TurretDeployable}
import net.psforever.packet.game.{PlanetSideGUID, TriggeredEffect, TriggeredEffectLocation}
import net.psforever.objects.vital.Vitality
import net.psforever.types.Vector3
import services.local.support.{DeployableRemover, DoorCloseActor, HackClearActor, HackCaptureActor}
import services.vehicle.{VehicleAction, VehicleServiceMessage}
import services.{GenericEventBus, Service, ServiceManager}
import services.local.support.{DoorCloseActor, HackClearActor}
import scala.util.Success
import scala.concurrent.duration._
import akka.pattern.ask
import net.psforever.objects.GlobalDefinitions
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
import net.psforever.objects.serverobject.structures.{Amenity, Building}
import net.psforever.objects.serverobject.terminals.CaptureTerminal
import net.psforever.packet.game.PlanetSideGUID
import services.ServiceManager.Lookup
import scala.concurrent.duration.Duration
class LocalService extends Actor {
private val doorCloser = context.actorOf(Props[DoorCloseActor], "local-door-closer")
private val hackClearer = context.actorOf(Props[HackClearActor], "local-hack-clearer")
private val hackCapturer = context.actorOf(Props[HackCaptureActor], "local-hack-capturer")
private val engineer = context.actorOf(Props[DeployableRemover], "deployable-remover-agent")
private [this] val log = org.log4s.getLogger
var cluster : ActorRef = Actor.noSender
@ -59,6 +61,14 @@ class LocalService extends Actor {
case LocalServiceMessage(forChannel, action) =>
action match {
case LocalAction.AlertDestroyDeployable(_, obj) =>
LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/Local", Service.defaultPlayerGUID, LocalResponse.AlertDestroyDeployable(obj))
)
case LocalAction.DeployableMapIcon(player_guid, behavior, deployInfo) =>
LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.DeployableMapIcon(behavior, deployInfo))
)
case LocalAction.DoorOpens(player_guid, zone, door) =>
doorCloser ! DoorCloseActor.DoorIsOpen(door, zone)
LocalEvents.publish(
@ -77,7 +87,7 @@ class LocalService extends Actor {
LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.HackObject(target.GUID, unk1, unk2))
)
case LocalAction.ClearTemporaryHack(player_guid, target) =>
case LocalAction.ClearTemporaryHack(_, target) =>
hackClearer ! HackClearActor.ObjectIsResecured(target)
case LocalAction.HackCaptureTerminal(player_guid, zone, target, unk1, unk2, isResecured) =>
@ -101,14 +111,26 @@ class LocalService extends Actor {
LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.ProximityTerminalEffect(object_guid, effectState))
)
case LocalAction.SetEmpire(object_guid, empire) =>
LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/Local", Service.defaultPlayerGUID, LocalResponse.SetEmpire(object_guid, empire))
)
case LocalAction.TriggerEffect(player_guid, effect, target) =>
LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.TriggerEffect(target, effect))
)
case LocalAction.TriggerEffectLocation(player_guid, effect, pos, orient) =>
LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.TriggerEffect(PlanetSideGUID(0), effect, None, Some(TriggeredEffectLocation(pos, orient))))
)
case LocalAction.TriggerEffectInfo(player_guid, effect, target, unk1, unk2) =>
LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.TriggerEffect(target, effect, Some(TriggeredEffect(unk1, unk2))))
)
case LocalAction.TriggerSound(player_guid, sound, pos, unk, volume) =>
LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.TriggerSound(sound, pos, unk, volume))
)
case LocalAction.SetEmpire(object_guid, empire) =>
LocalEvents.publish(
LocalServiceResponse(s"/$forChannel/Local", PlanetSideGUID(-1), LocalResponse.SetEmpire(object_guid, empire))
)
case _ => ;
}
@ -120,21 +142,21 @@ class LocalService extends Actor {
//response from HackClearActor
case HackClearActor.ClearTheHack(target_guid, zone_id, unk1, unk2) =>
log.warn(s"Clearing hack for ${target_guid}")
log.warn(s"Clearing hack for $target_guid")
LocalEvents.publish(
LocalServiceResponse(s"/$zone_id/Local", Service.defaultPlayerGUID, LocalResponse.HackClear(target_guid, unk1, unk2))
)
case HackCaptureActor.HackTimeoutReached(capture_terminal_guid, zone_id, unk1, unk2, hackedByFaction) =>
case HackCaptureActor.HackTimeoutReached(capture_terminal_guid, zone_id, _, _, hackedByFaction) =>
import scala.concurrent.ExecutionContext.Implicits.global
ask(cluster, InterstellarCluster.GetWorld(zone_id))(1 seconds).onComplete {
case Success(InterstellarCluster.GiveWorld(zoneId, zone)) =>
case Success(InterstellarCluster.GiveWorld(_, zone)) =>
val terminal = zone.asInstanceOf[Zone].GUID(capture_terminal_guid).get.asInstanceOf[CaptureTerminal]
val building = terminal.Owner.asInstanceOf[Building]
// todo: Move this to a function for Building
var ntuLevel = 0
building.Amenities.filter(x => (x.Definition == GlobalDefinitions.resource_silo)).headOption.asInstanceOf[Option[ResourceSilo]] match {
building.Amenities.find(_.Definition == GlobalDefinitions.resource_silo).asInstanceOf[Option[ResourceSilo]] match {
case Some(obj: ResourceSilo) =>
ntuLevel = obj.CapacitorDisplay.toInt
case _ =>
@ -143,7 +165,7 @@ class LocalService extends Actor {
}
if(ntuLevel > 0) {
log.info(s"Setting base ${building.ModelId} as owned by ${hackedByFaction}")
log.info(s"Setting base ${building.ModelId} as owned by $hackedByFaction")
building.Faction = hackedByFaction
self ! LocalServiceMessage(zone.Id, LocalAction.SetEmpire(PlanetSideGUID(building.ModelId), hackedByFaction))
@ -160,9 +182,86 @@ class LocalService extends Actor {
case scala.util.Failure(_) => log.warn(s"LocalService Failed to get zone when hack timeout was reached")
}
case HackCaptureActor.GetHackTimeRemainingNanos(capture_console_guid) =>
hackCapturer forward HackCaptureActor.GetHackTimeRemainingNanos(capture_console_guid)
//message to Engineer
case LocalServiceMessage.Deployables(msg) =>
engineer forward msg
//message(s) from Engineer
case msg @ DeployableRemover.EliminateDeployable(obj : TurretDeployable, guid, pos, zone) =>
val seats = obj.Seats.values
if(seats.count(_.isOccupied) > 0) {
val wasKickedByDriver = false //TODO yeah, I don't know
seats.foreach(seat => {
seat.Occupant match {
case Some(tplayer) =>
seat.Occupant = None
tplayer.VehicleSeated = None
zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.KickPassenger(tplayer.GUID, 4, wasKickedByDriver, obj.GUID))
case None => ;
}
})
import scala.concurrent.ExecutionContext.Implicits.global
context.system.scheduler.scheduleOnce(Duration.create(2, "seconds"), self, msg)
}
else {
EliminateDeployable(obj, guid, pos, zone.Id)
}
case DeployableRemover.EliminateDeployable(obj : BoomerDeployable, guid, pos, zone) =>
EliminateDeployable(obj, guid, pos, zone.Id)
obj.Trigger match {
case Some(trigger) =>
log.warn(s"LocalService: deconstructing boomer in ${zone.Id}, but trigger@${trigger.GUID.guid} still exists")
case _ => ;
}
case DeployableRemover.EliminateDeployable(obj, guid, pos, zone) =>
EliminateDeployable(obj, guid, pos, zone.Id)
case DeployableRemover.DeleteTrigger(trigger_guid, zone) =>
LocalEvents.publish(
LocalServiceResponse(s"/${zone.Id}/Local", Service.defaultPlayerGUID, LocalResponse.ObjectDelete(trigger_guid, 0))
)
//synchronized damage calculations
case Vitality.DamageOn(target : Deployable, func) =>
func(target)
sender ! Vitality.DamageResolution(target)
case msg =>
log.info(s"Unhandled message $msg from $sender")
log.warn(s"Unhandled message $msg from $sender")
}
/**
* Common behavior for distributing information about a deployable's destruction or deconstruction.<br>
* <br>
* The primary distribution task instructs all clients to eliminate the target deployable.
* This is a cosmetic exercise as the deployable should already be unregistered from its zone and
* functionally removed from its zone's list of deployable objects by external operations.
* The other distribution is a targeted message sent to the former owner of the deployable
* if he still exists on the server
* to clean up any leftover ownership-specific knowledge about the deployable.
* @see `DeployableRemover`
* @param obj the deployable object
* @param guid the deployable objects globally unique identifier;
* may be a former identifier
* @param position the deployable's position
* @param zoneId the zone where the deployable is currently placed
*/
def EliminateDeployable(obj : PlanetSideGameObject with Deployable, guid : PlanetSideGUID, position : Vector3, zoneId : String) : Unit = {
LocalEvents.publish(
LocalServiceResponse(s"/$zoneId/Local", Service.defaultPlayerGUID, LocalResponse.EliminateDeployable(obj, guid, position))
)
obj.OwnerName match {
case Some(name) =>
LocalEvents.publish(
LocalServiceResponse(s"/$name/Local", Service.defaultPlayerGUID, LocalResponse.AlertDestroyDeployable(obj))
)
case None => ;
}
}
}

View file

@ -2,3 +2,7 @@
package services.local
final case class LocalServiceMessage(forChannel : String, actionMessage : LocalAction.Action)
object LocalServiceMessage {
final case class Deployables(msg : Any)
}

View file

@ -0,0 +1,86 @@
// Copyright (c) 2017 PSForever
package services.local.support
import net.psforever.objects.ce.Deployable
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
import net.psforever.objects.zones.Zone
import net.psforever.objects.{BoomerDeployable, PlanetSideGameObject, TurretDeployable}
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.Vector3
import services.RemoverActor
import scala.concurrent.duration._
class DeployableRemover extends RemoverActor {
final val FirstStandardDuration : FiniteDuration = 3 minutes
final val SecondStandardDuration : FiniteDuration = 2 seconds
def InclusionTest(entry : RemoverActor.Entry) : Boolean = entry.obj.isInstanceOf[Deployable]
def InitialJob(entry : RemoverActor.Entry) : Unit = { }
def FirstJob(entry : RemoverActor.Entry) : Unit = {
val obj = entry.obj
obj match {
case boomer : BoomerDeployable =>
FirstJobBoomer(boomer, entry)
case _ => ;
}
entry.zone.Deployables ! Zone.Deployable.Dismiss(obj.asInstanceOf[PlanetSideGameObject with Deployable])
}
def FirstJobBoomer(obj : BoomerDeployable, entry : RemoverActor.Entry) : Unit = {
obj.Trigger match {
case Some(trigger) =>
if(trigger.HasGUID) {
val guid = trigger.GUID
Zone.EquipmentIs.Where(trigger, guid, entry.zone) match {
case Some(Zone.EquipmentIs.InContainer(container, index)) =>
container.Slot(index).Equipment = None
case Some(Zone.EquipmentIs.OnGround()) =>
entry.zone.Ground ! Zone.Ground.RemoveItem(guid)
case _ => ;
}
context.parent ! DeployableRemover.DeleteTrigger(guid, entry.zone)
}
case None => ;
}
}
override def SecondJob(entry : RemoverActor.Entry) : Unit = {
val obj = entry.obj.asInstanceOf[PlanetSideGameObject with Deployable]
info(s"Deleting a ${obj.Definition.Name} deployable")
context.parent ! DeployableRemover.EliminateDeployable(obj, obj.GUID, obj.Position, entry.zone)
super.SecondJob(entry)
}
def ClearanceTest(entry : RemoverActor.Entry) : Boolean = !entry.zone.DeployableList.contains(entry.obj)
def DeletionTask(entry : RemoverActor.Entry) : TaskResolver.GiveTask = {
entry.obj match {
case turret : TurretDeployable =>
GUIDTask.UnregisterDeployableTurret(turret)(entry.zone.GUID)
case boomer : BoomerDeployable =>
boomer.Trigger match {
case Some(trigger) =>
boomer.Trigger = None
taskResolver ! GUIDTask.UnregisterObjectTask(trigger)(entry.zone.GUID)
case None => ;
}
GUIDTask.UnregisterObjectTask(boomer)(entry.zone.GUID)
case obj =>
GUIDTask.UnregisterObjectTask(obj)(entry.zone.GUID)
}
}
}
object DeployableRemover {
final case class EliminateDeployable(obj : PlanetSideGameObject with Deployable,
guid : PlanetSideGUID,
position : Vector3,
zone : Zone)
final case class DeleteTrigger(trigger_guid : PlanetSideGUID,
zone : Zone)
}

View file

@ -4,7 +4,7 @@ package services.vehicle.support
import akka.actor.{Actor, ActorRef, Cancellable}
import net.psforever.objects.{AmmoBox, DefaultCancellable, PlanetSideGameObject, Tool}
import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver}
import net.psforever.objects.serverobject.turret.{MannedTurret, TurretUpgrade}
import net.psforever.objects.serverobject.turret.{FacilityTurret, TurretUpgrade}
import net.psforever.objects.vehicles.MountedWeapons
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.PlanetSideGUID
@ -50,7 +50,7 @@ class TurretUpgrader extends SupportActor[TurretUpgrader.Entry] {
def CreateEntry(obj : PlanetSideGameObject, zone : Zone, upgrade : TurretUpgrade.Value, duration : Long) = TurretUpgrader.Entry(obj, zone, upgrade, duration)
def InclusionTest(entry : TurretUpgrader.Entry) : Boolean = entry.obj.isInstanceOf[MannedTurret]
def InclusionTest(entry : TurretUpgrader.Entry) : Boolean = entry.obj.isInstanceOf[FacilityTurret]
def receive : Receive = {
case Service.Startup() =>
@ -157,7 +157,7 @@ class TurretUpgrader extends SupportActor[TurretUpgrader.Entry] {
* @param entry na
*/
def UpgradeTurretAmmo(entry : TurretUpgrader.Entry) : Unit = {
val target = entry.obj.asInstanceOf[MannedTurret]
val target = entry.obj.asInstanceOf[FacilityTurret]
val zone = entry.zone
val zoneId = zone.Id
val upgrade = entry.upgrade
@ -222,7 +222,7 @@ class TurretUpgrader extends SupportActor[TurretUpgrader.Entry] {
* @param entry na
*/
def FinishUpgradingTurret(entry : TurretUpgrader.Entry)() : Unit = {
val target = entry.obj.asInstanceOf[MannedTurret]
val target = entry.obj.asInstanceOf[FacilityTurret]
val zone = entry.zone
info(s"Wall turret finished ${target.Upgrade} upgrade")
val targetGUID = target.GUID
@ -250,7 +250,7 @@ object TurretUpgrader extends SupportActorCaseConversions {
*/
case class Entry(_obj : PlanetSideGameObject, _zone : Zone, upgrade : TurretUpgrade.Value, _duration : Long) extends SupportActor.Entry(_obj, _zone, _duration)
final case class AddTask(turret : MannedTurret, zone : Zone, upgrade : TurretUpgrade.Value, duration : Option[FiniteDuration] = None)
final case class AddTask(turret : FacilityTurret, zone : Zone, upgrade : TurretUpgrade.Value, duration : Option[FiniteDuration] = None)
final case class Downgrade()

View file

@ -12,6 +12,14 @@ class Vector3Test extends Specification {
vec.z mustEqual 3.9f
}
"isolate x,y components" in {
vec.xy mustEqual Vector3(1.3f, -2.6f, 0)
}
"promote float values into a specific z-format" in {
Vector3.z(3.9f) mustEqual Vector3(0, 0, 3.9f)
}
"calculate magnitude (like a vector) 1" in {
val obj = Vector3(2.0f, 0.0f, 0.0f)
Vector3.Magnitude(obj) mustEqual 2.0f

View file

@ -12,15 +12,11 @@ class DeployObjectMessageTest extends Specification {
"decode" in {
PacketCoding.DecodePacket(string).require match {
case DeployObjectMessage(guid, unk1, pos, roll, pitch, yaw, unk2) =>
case DeployObjectMessage(guid, unk1, pos, orient, unk2) =>
guid mustEqual PlanetSideGUID(2932)
unk1 mustEqual 1000L
pos.x mustEqual 5769.297f
pos.y mustEqual 3192.8594f
pos.z mustEqual 97.96875f
roll mustEqual 0
pitch mustEqual 0
yaw mustEqual 63
pos mustEqual Vector3(5769.297f, 3192.8594f, 97.96875f)
orient mustEqual Vector3.z(272.8125f)
unk2 mustEqual 1L
case _ =>
ko
@ -28,7 +24,7 @@ class DeployObjectMessageTest extends Specification {
}
"encode" in {
val msg = DeployObjectMessage(PlanetSideGUID(2932), 1000L, Vector3(5769.297f, 3192.8594f, 97.96875f), 0, 0, 63, 1L)
val msg = DeployObjectMessage(PlanetSideGUID(2932), 1000L, Vector3(5769.297f, 3192.8594f, 97.96875f), Vector3.z(272.8125f), 1L)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string

View file

@ -11,10 +11,10 @@ class ObjectDeployedMessageTest extends Specification {
"decode" in {
PacketCoding.DecodePacket(string_boomer).require match {
case ObjectDeployedMessage(unk : Int, desc : String, act : DeploymentOutcome.Value, count : Long, max : Long) =>
case ObjectDeployedMessage(unk : Int, desc : String, act : DeployOutcome.Value, count : Long, max : Long) =>
unk mustEqual 0
desc mustEqual "boomer"
act mustEqual DeploymentOutcome.Success
act mustEqual DeployOutcome.Success
count mustEqual 1
max mustEqual 25
case _ =>
@ -23,7 +23,7 @@ class ObjectDeployedMessageTest extends Specification {
}
"encode" in {
val msg = ObjectDeployedMessage("boomer", DeploymentOutcome.Success, 1, 25)
val msg = ObjectDeployedMessage("boomer", DeployOutcome.Success, 1, 25)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_boomer

View file

@ -10,6 +10,7 @@ import scodec.bits._
class TriggerEffectMessageTest extends Specification {
val string_motionalarmsensor = hex"51 970B 82 6F6E FA00C00000"
val string_boomer = hex"51 0000 93 737061776E5F6F626A6563745F656666656374 417BB2CB3B4F8E00000000"
val string_boomer_explode = hex"51 DF09 8F 6465746F6E6174655F626F6F6D6572 00"
"decode (motion alarm sensor)" in {
PacketCoding.DecodePacket(string_motionalarmsensor).require match {
@ -32,42 +33,44 @@ class TriggerEffectMessageTest extends Specification {
effect mustEqual "spawn_object_effect"
unk.isDefined mustEqual false
location.isDefined mustEqual true
location.get.pos.x mustEqual 3567.0156f
location.get.pos.y mustEqual 3278.6953f
location.get.pos.z mustEqual 114.484375f
location.get.roll mustEqual 0
location.get.pitch mustEqual 0
location.get.yaw mustEqual 0
location.get.pos mustEqual Vector3(3567.0156f, 3278.6953f, 114.484375f)
location.get.orient mustEqual Vector3(0, 0, 90)
case _ =>
ko
}
}
"decode (boomer explode)" in {
PacketCoding.DecodePacket(string_boomer_explode).require match {
case TriggerEffectMessage(guid, effect, unk, location) =>
guid mustEqual PlanetSideGUID(2527)
effect mustEqual "detonate_boomer"
unk.isDefined mustEqual false
location.isDefined mustEqual false
case _ =>
ko
}
}
"encode (motion alarm sensor)" in {
val msg = TriggerEffectMessage(
PlanetSideGUID(2967),
"on",
Some(TriggeredEffect(true, 1000L)),
None
)
val msg = TriggerEffectMessage(PlanetSideGUID(2967), "on", true, 1000L)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_motionalarmsensor
}
"encode (boomer)" in {
val msg = TriggerEffectMessage(
PlanetSideGUID(0),
"spawn_object_effect",
None,
Some(TriggeredEffectLocation(
Vector3(3567.0156f, 3278.6953f, 114.484375f),
0, 0, 0
))
)
val msg = TriggerEffectMessage("spawn_object_effect", Vector3(3567.0156f, 3278.6953f, 114.484375f), Vector3(0, 0, 90))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_boomer
}
"encode (boomer explode)" in {
val msg = TriggerEffectMessage(PlanetSideGUID(2527), "detonate_boomer")
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_boomer_explode
}
}

View file

@ -4,7 +4,7 @@ package game.objectcreate
import net.psforever.packet.PacketCoding
import net.psforever.packet.game.{ObjectCreateMessage, PlanetSideGUID}
import net.psforever.packet.game.objectcreate._
import net.psforever.types.PlanetSideEmpire
import net.psforever.types.{PlanetSideEmpire, Vector3}
import org.specs2.mutable._
import scodec.bits._
@ -22,12 +22,8 @@ class AegisShieldGeneratorDataTest extends Specification {
data.isDefined mustEqual true
data.get.isInstanceOf[AegisShieldGeneratorData] mustEqual true
val aegis = data.get.asInstanceOf[AegisShieldGeneratorData]
aegis.deploy.pos.coord.x mustEqual 3571.2266f
aegis.deploy.pos.coord.y mustEqual 3278.0938f
aegis.deploy.pos.coord.z mustEqual 114.0f
aegis.deploy.pos.orient.x mustEqual 0f
aegis.deploy.pos.orient.y mustEqual 0f
aegis.deploy.pos.orient.z mustEqual 90.0f
aegis.deploy.pos.coord mustEqual Vector3(3571.2266f, 3278.0938f, 114.0f)
aegis.deploy.pos.orient mustEqual Vector3(0, 0, 90)
aegis.deploy.faction mustEqual PlanetSideEmpire.VS
aegis.deploy.unk mustEqual 2
aegis.health mustEqual 255
@ -40,7 +36,7 @@ class AegisShieldGeneratorDataTest extends Specification {
"encode" in {
val obj = AegisShieldGeneratorData(
CommonFieldData(
PlacementData(3571.2266f, 3278.0938f, 114.0f, 0f, 0f, 90.0f),
PlacementData(Vector3(3571.2266f, 3278.0938f, 114.0f), Vector3(0, 0, 90)),
PlanetSideEmpire.VS, 2, PlanetSideGUID(2366)
),
255

View file

@ -4,7 +4,7 @@ package game.objectcreate
import net.psforever.packet.PacketCoding
import net.psforever.packet.game.{ObjectCreateMessage, PlanetSideGUID}
import net.psforever.packet.game.objectcreate._
import net.psforever.types.PlanetSideEmpire
import net.psforever.types.{PlanetSideEmpire, Vector3}
import org.specs2.mutable._
import scodec.bits._
@ -22,23 +22,19 @@ class OneMannedFieldTurretDataTest extends Specification {
data.isDefined mustEqual true
data.get.isInstanceOf[OneMannedFieldTurretData] mustEqual true
val omft = data.get.asInstanceOf[OneMannedFieldTurretData]
omft.deploy.pos.coord.x mustEqual 3567.1406f
omft.deploy.pos.coord.y mustEqual 2988.0078f
omft.deploy.pos.coord.z mustEqual 71.84375f
omft.deploy.pos.orient.x mustEqual 0f
omft.deploy.pos.orient.y mustEqual 0f
omft.deploy.pos.orient.z mustEqual 185.625f
omft.deploy.pos.coord mustEqual Vector3(3567.1406f, 2988.0078f, 71.84375f)
omft.deploy.pos.orient mustEqual Vector3(0, 0, 185.625f)
omft.deploy.faction mustEqual PlanetSideEmpire.VS
omft.deploy.unk mustEqual 2
omft.deploy.player_guid mustEqual PlanetSideGUID(2502)
omft.deploy.unk1 mustEqual 2
omft.deploy.owner_guid mustEqual PlanetSideGUID(2502)
omft.health mustEqual 255
omft.internals.isDefined mustEqual true
val internals = omft.internals.get
internals.objectClass mustEqual ObjectClass.energy_gun_vs
internals.guid mustEqual PlanetSideGUID(2615)
internals.parentSlot mustEqual 1
internals.obj.isInstanceOf[WeaponData] mustEqual true
val wep = internals.obj.asInstanceOf[WeaponData]
val internals = omft.internals.get.contents
internals.head.objectClass mustEqual ObjectClass.energy_gun_vs
internals.head.guid mustEqual PlanetSideGUID(2615)
internals.head.parentSlot mustEqual 1
internals.head.obj.isInstanceOf[WeaponData] mustEqual true
val wep = internals.head.obj.asInstanceOf[WeaponData]
wep.unk1 mustEqual 0x6
wep.unk2 mustEqual 0x8
wep.fire_mode mustEqual 0
@ -55,20 +51,16 @@ class OneMannedFieldTurretDataTest extends Specification {
"encode (orion)" in {
val obj = OneMannedFieldTurretData(
CommonFieldData(
PlacementData(3567.1406f, 2988.0078f, 71.84375f, 0f, 0f, 185.625f),
PlanetSideEmpire.VS, 2, PlanetSideGUID(2502)
SmallDeployableData(
PlacementData(Vector3(3567.1406f, 2988.0078f, 71.84375f), Vector3(0, 0, 185.625f)),
PlanetSideEmpire.VS, false, false, 2, false, false, PlanetSideGUID(2502)
),
255,
OneMannedFieldTurretData.orion(PlanetSideGUID(2615), 0x6, 0x8, PlanetSideGUID(2510), 8)
InventoryData(List(OneMannedFieldTurretData.orion(PlanetSideGUID(2615), 0x6, 0x8, PlanetSideGUID(2510), 8)))
)
val msg = ObjectCreateMessage(ObjectClass.portable_manned_turret_vs, PlanetSideGUID(2916), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
val pkt_bitv = pkt.toBitVector
val ori_bitv = string_orion.toBitVector
pkt_bitv.take(189) mustEqual ori_bitv.take(189)
pkt_bitv.drop(200) mustEqual ori_bitv.drop(200)
//TODO work on OneMannedFieldTurretData to make this pass as a single stream
pkt mustEqual string_orion
}
"avenger" in {

View file

@ -4,7 +4,7 @@ package game.objectcreate
import net.psforever.packet.PacketCoding
import net.psforever.packet.game.{ObjectCreateMessage, PlanetSideGUID}
import net.psforever.packet.game.objectcreate._
import net.psforever.types.PlanetSideEmpire
import net.psforever.types.{PlanetSideEmpire, Vector3}
import org.specs2.mutable._
import scodec.bits._
@ -22,14 +22,14 @@ class SmallDeployableDataTest extends Specification {
data.isDefined mustEqual true
data.get.isInstanceOf[SmallDeployableData] mustEqual true
val boomer = data.get.asInstanceOf[SmallDeployableData]
boomer.deploy.pos.coord.x mustEqual 4704.172f
boomer.deploy.pos.coord.y mustEqual 5546.4375f
boomer.deploy.pos.coord.z mustEqual 82.234375f
boomer.deploy.pos.orient.x mustEqual 0f
boomer.deploy.pos.orient.y mustEqual 0f
boomer.deploy.pos.orient.z mustEqual 272.8125f
boomer.deploy.unk mustEqual 0
boomer.deploy.player_guid mustEqual PlanetSideGUID(4145)
boomer.pos.coord.x mustEqual 4704.172f
boomer.pos.coord.y mustEqual 5546.4375f
boomer.pos.coord.z mustEqual 82.234375f
boomer.pos.orient.x mustEqual 0f
boomer.pos.orient.y mustEqual 0f
boomer.pos.orient.z mustEqual 272.8125f
boomer.unk1 mustEqual 0
boomer.owner_guid mustEqual PlanetSideGUID(8290)
case _ =>
ko
}
@ -37,10 +37,8 @@ class SmallDeployableDataTest extends Specification {
"encode (boomer)" in {
val obj = SmallDeployableData(
CommonFieldData(
PlacementData(4704.172f, 5546.4375f, 82.234375f, 0f, 0f, 272.8125f),
PlanetSideEmpire.TR, 0, PlanetSideGUID(4145)
)
PlacementData(Vector3(4704.172f, 5546.4375f, 82.234375f), Vector3.z(272.8125f)),
PlanetSideEmpire.TR, false, false, 0, false, false, PlanetSideGUID(8290)
)
val msg = ObjectCreateMessage(ObjectClass.boomer, PlanetSideGUID(3840), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector

View file

@ -3,8 +3,8 @@ package game.objectcreate
import net.psforever.packet.PacketCoding
import net.psforever.packet.game.{ObjectCreateMessage, PlanetSideGUID}
import net.psforever.packet.game.objectcreate._
import net.psforever.types.PlanetSideEmpire
import net.psforever.packet.game.objectcreate.{SmallDeployableData, _}
import net.psforever.types.{PlanetSideEmpire, Vector3}
import org.specs2.mutable._
import scodec.bits._
@ -23,16 +23,12 @@ class SmallTurretDataTest extends Specification {
data.isDefined mustEqual true
data.get.isInstanceOf[SmallTurretData] mustEqual true
val turret = data.get.asInstanceOf[SmallTurretData]
turret.deploy.pos.coord.x mustEqual 4577.7812f
turret.deploy.pos.coord.y mustEqual 5624.828f
turret.deploy.pos.coord.z mustEqual 72.046875f
turret.deploy.pos.orient.x mustEqual 0f
turret.deploy.pos.orient.y mustEqual 2.8125f
turret.deploy.pos.orient.z mustEqual 264.375f
turret.deploy.pos.coord mustEqual Vector3(4577.7812f, 5624.828f, 72.046875f)
turret.deploy.pos.orient mustEqual Vector3(0, 2.8125f, 264.375f)
turret.deploy.faction mustEqual PlanetSideEmpire.NC
turret.deploy.destroyed mustEqual true
turret.deploy.unk mustEqual 2
turret.deploy.player_guid mustEqual PlanetSideGUID(3871)
turret.deploy.unk1 mustEqual 2
turret.deploy.owner_guid mustEqual PlanetSideGUID(7742)
turret.health mustEqual 0
turret.internals.isDefined mustEqual false
case _ =>
@ -50,23 +46,19 @@ class SmallTurretDataTest extends Specification {
data.isDefined mustEqual true
data.get.isInstanceOf[SmallTurretData] mustEqual true
val turret = data.get.asInstanceOf[SmallTurretData]
turret.deploy.pos.coord.x mustEqual 4527.633f
turret.deploy.pos.coord.y mustEqual 6271.3594f
turret.deploy.pos.coord.z mustEqual 70.265625f
turret.deploy.pos.orient.x mustEqual 0f
turret.deploy.pos.orient.y mustEqual 0f
turret.deploy.pos.orient.z mustEqual 154.6875f
turret.deploy.pos.coord mustEqual Vector3(4527.633f, 6271.3594f, 70.265625f)
turret.deploy.pos.orient mustEqual Vector3(0, 0, 154.6875f)
turret.deploy.faction mustEqual PlanetSideEmpire.VS
turret.deploy.unk mustEqual 2
turret.deploy.player_guid mustEqual PlanetSideGUID(4232)
turret.deploy.unk1 mustEqual 2
turret.deploy.owner_guid mustEqual PlanetSideGUID(8208)
turret.health mustEqual 255
turret.internals.isDefined mustEqual true
val internals = turret.internals.get
internals.objectClass mustEqual ObjectClass.spitfire_weapon
internals.guid mustEqual PlanetSideGUID(3064)
internals.parentSlot mustEqual 0
internals.obj.isInstanceOf[WeaponData] mustEqual true
val wep = internals.obj.asInstanceOf[WeaponData]
val internals = turret.internals.get.contents
internals.head.objectClass mustEqual ObjectClass.spitfire_weapon
internals.head.guid mustEqual PlanetSideGUID(3064)
internals.head.parentSlot mustEqual 0
internals.head.obj.isInstanceOf[WeaponData] mustEqual true
val wep = internals.head.obj.asInstanceOf[WeaponData]
wep.unk1 mustEqual 0x6
wep.unk2 mustEqual 0x8
wep.fire_mode mustEqual 0
@ -83,37 +75,29 @@ class SmallTurretDataTest extends Specification {
"encode (spitfire, short)" in {
val obj = SmallTurretData(
CommonFieldData(
PlacementData(4577.7812f, 5624.828f, 72.046875f, 0f, 2.8125f, 264.375f),
PlanetSideEmpire.NC, true, 2, PlanetSideGUID(3871)
SmallDeployableData(
PlacementData(Vector3(4577.7812f, 5624.828f, 72.046875f), Vector3(0, 2.8125f, 264.375f)),
PlanetSideEmpire.NC, false, true, 2, false, false, PlanetSideGUID(7742)
),
255 //sets to 0
)
val msg = ObjectCreateMessage(ObjectClass.spitfire_turret, PlanetSideGUID(4208), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
val pkt_bitv = pkt.toBitVector
val ori_bitv = string_spitfire_short.toBitVector
pkt_bitv.take(173) mustEqual ori_bitv.take(173)
pkt_bitv.drop(185) mustEqual ori_bitv.drop(185)
//TODO work on SmallTurretData to make this pass as a single stream
pkt mustEqual string_spitfire_short
}
"encode (spitfire)" in {
val obj = SmallTurretData(
CommonFieldData(
PlacementData(4527.633f, 6271.3594f, 70.265625f, 0f, 0f, 154.6875f),
PlanetSideEmpire.VS, 2, PlanetSideGUID(4232)
SmallDeployableData(
PlacementData(Vector3(4527.633f, 6271.3594f, 70.265625f), Vector3(0, 0, 154.6875f)),
PlanetSideEmpire.VS, false, false, 2, false, true, PlanetSideGUID(8208)
),
255,
SmallTurretData.spitfire(PlanetSideGUID(3064), 0x6, 0x8, PlanetSideGUID(3694), 8)
InventoryData(List(SmallTurretData.spitfire(PlanetSideGUID(3064), 0x6, 0x8, PlanetSideGUID(3694), 8)))
)
val msg = ObjectCreateMessage(ObjectClass.spitfire_turret, PlanetSideGUID(4265), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
val pkt_bitv = pkt.toBitVector
val ori_bitv = string_spitfire.toBitVector
pkt_bitv.take(173) mustEqual ori_bitv.take(173)
pkt_bitv.drop(185) mustEqual ori_bitv.drop(185)
//TODO work on SmallTurretData to make this pass as a single stream
pkt mustEqual string_spitfire
}
}
}

View file

@ -4,7 +4,7 @@ package game.objectcreate
import net.psforever.packet.PacketCoding
import net.psforever.packet.game.{ObjectCreateMessage, PlanetSideGUID}
import net.psforever.packet.game.objectcreate._
import net.psforever.types.PlanetSideEmpire
import net.psforever.types.{PlanetSideEmpire, Vector3}
import org.specs2.mutable._
import scodec.bits._
@ -22,16 +22,11 @@ class TRAPDataTest extends Specification {
data.isDefined mustEqual true
data.get.isInstanceOf[TRAPData] mustEqual true
val trap = data.get.asInstanceOf[TRAPData]
trap.deploy.pos.coord.x mustEqual 3572.4453f
trap.deploy.pos.coord.y mustEqual 3277.9766f
trap.deploy.pos.coord.z mustEqual 114.0f
trap.deploy.pos.orient.x mustEqual 0f
trap.deploy.pos.orient.y mustEqual 0f
trap.deploy.pos.orient.z mustEqual 90.0f
trap.deploy.pos.coord mustEqual Vector3(3572.4453f, 3277.9766f, 114.0f)
trap.deploy.pos.orient mustEqual Vector3(0, 0, 90)
trap.deploy.faction mustEqual PlanetSideEmpire.VS
trap.deploy.unk mustEqual 2
trap.deploy.owner_guid mustEqual PlanetSideGUID(4748)
trap.health mustEqual 255
trap.deploy.player_guid mustEqual PlanetSideGUID(2502)
case _ =>
ko
}
@ -39,9 +34,9 @@ class TRAPDataTest extends Specification {
"encode" in {
val obj = TRAPData(
CommonFieldData(
SmallDeployableData(
PlacementData(3572.4453f, 3277.9766f, 114.0f, 0f, 0f, 90.0f),
PlanetSideEmpire.VS, 2, PlanetSideGUID(2502)
PlanetSideEmpire.VS, false, false, 2, false, true, PlanetSideGUID(4748)
),
255
)

View file

@ -1,15 +1,13 @@
// Copyright (c) 2017 PSForever
package objects
import net.psforever.objects.definition.converter.{ACEConverter, CharacterSelectConverter, DestroyedVehicleConverter, REKConverter}
import net.psforever.objects.definition.converter.{CharacterSelectConverter, DestroyedVehicleConverter, REKConverter}
import net.psforever.objects._
import net.psforever.objects.definition._
import net.psforever.objects.equipment.CItem.{DeployedItem, Unit}
import net.psforever.objects.equipment._
import net.psforever.objects.inventory.InventoryTile
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.vehicles.DestroyedVehicle
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.packet.game.objectcreate._
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire, Vector3}
@ -109,13 +107,7 @@ class ConverterTest extends Specification {
"ConstructionItem" should {
"convert to packet" in {
val cdef = ConstructionItemDefinition(Unit.advanced_ace)
cdef.Modes += DeployedItem.tank_traps
cdef.Modes += DeployedItem.portable_manned_turret_tr
cdef.Modes += DeployedItem.deployable_shield_generator
cdef.Tile = InventoryTile.Tile63
cdef.Packet = new ACEConverter()
val obj = ConstructionItem(cdef)
val obj = ConstructionItem(GlobalDefinitions.ace)
obj.GUID = PlanetSideGUID(90)
obj.Definition.Packet.DetailedConstructorData(obj) match {
case Success(pkt) =>
@ -123,6 +115,7 @@ class ConverterTest extends Specification {
case _ =>
ko
}
obj.Definition.Packet.ConstructorData(obj) match {
case Success(pkt) =>
pkt mustEqual ACEData(0,0)
@ -154,6 +147,158 @@ class ConverterTest extends Specification {
}
}
"BoomerTrigger" should {
"convert" in {
val obj = new BoomerTrigger
obj.GUID = PlanetSideGUID(90)
obj.Definition.Packet.DetailedConstructorData(obj) match {
case Success(pkt) =>
pkt mustEqual DetailedBoomerTriggerData()
case _ =>
ko
}
obj.Definition.Packet.ConstructorData(obj) match {
case Success(pkt) =>
pkt mustEqual BoomerTriggerData()
case _ =>
ko
}
}
}
"SmallDeployable" should {
"convert" in {
val obj = new SensorDeployable(GlobalDefinitions.motionalarmsensor)
obj.Faction = PlanetSideEmpire.TR
obj.Definition.Packet.DetailedConstructorData(obj).isFailure mustEqual true
obj.Definition.Packet.ConstructorData(obj) match {
case Success(pkt) =>
pkt mustEqual SmallDeployableData(
PlacementData(Vector3.Zero, Vector3.Zero),
PlanetSideEmpire.TR,
0,
false,
false
)
case _ =>
ko
}
}
}
"SmallTurret" should {
"convert" in {
val obj = new TurretDeployable(GlobalDefinitions.spitfire_turret)
obj.Faction = PlanetSideEmpire.TR
obj.GUID = PlanetSideGUID(90)
obj.Weapons(1).Equipment.get.GUID = PlanetSideGUID(91)
obj.Weapons(1).Equipment.get.asInstanceOf[Tool].AmmoSlot.Box.GUID = PlanetSideGUID(92)
obj.Definition.Packet.DetailedConstructorData(obj).isFailure mustEqual true
obj.Definition.Packet.ConstructorData(obj) match {
case Success(pkt) =>
pkt mustEqual SmallTurretData(
SmallDeployableData(
PlacementData(Vector3.Zero, Vector3.Zero),
PlanetSideEmpire.TR,
0,
false,
false
),
255,
InventoryData(
List(InternalSlot(ObjectClass.spitfire_weapon, PlanetSideGUID(91), 1,
WeaponData(4, 8, ObjectClass.spitfire_ammo, PlanetSideGUID(92), 0, AmmoBoxData()))
)
)
)
case _ =>
ko
}
}
}
"FieldTurret" should {
"convert" in {
val obj = new TurretDeployable(GlobalDefinitions.portable_manned_turret_tr)
obj.Faction = PlanetSideEmpire.TR
obj.GUID = PlanetSideGUID(90)
obj.Weapons(1).Equipment.get.GUID = PlanetSideGUID(91)
obj.Weapons(1).Equipment.get.asInstanceOf[Tool].AmmoSlot.Box.GUID = PlanetSideGUID(92)
obj.Definition.Packet.DetailedConstructorData(obj).isFailure mustEqual true
obj.Definition.Packet.ConstructorData(obj) match {
case Success(pkt) =>
pkt mustEqual OneMannedFieldTurretData(
SmallDeployableData(
PlacementData(Vector3.Zero, Vector3.Zero),
PlanetSideEmpire.TR,
0,
false,
false
),
255,
InventoryData(
List(InternalSlot(ObjectClass.energy_gun_tr, PlanetSideGUID(91), 1,
WeaponData(4, 8, ObjectClass.energy_gun_ammo, PlanetSideGUID(92), 0, AmmoBoxData()))
)
)
)
case _ =>
ko
}
}
}
"TRAP" should {
"convert" in {
val obj = new TrapDeployable(GlobalDefinitions.tank_traps)
obj.Faction = PlanetSideEmpire.TR
obj.GUID = PlanetSideGUID(90)
obj.Definition.Packet.DetailedConstructorData(obj).isFailure mustEqual true
obj.Definition.Packet.ConstructorData(obj) match {
case Success(pkt) =>
pkt mustEqual TRAPData(
SmallDeployableData(
PlacementData(Vector3.Zero, Vector3.Zero),
PlanetSideEmpire.TR,
0,
false,
false
),
255
)
case _ =>
ko
}
}
}
"ShieldGenerator" should {
"convert" in {
val obj = new ShieldGeneratorDeployable(GlobalDefinitions.deployable_shield_generator)
obj.Faction = PlanetSideEmpire.TR
obj.GUID = PlanetSideGUID(90)
obj.Definition.Packet.DetailedConstructorData(obj).isFailure mustEqual true
obj.Definition.Packet.ConstructorData(obj) match {
case Success(pkt) =>
pkt mustEqual AegisShieldGeneratorData(
CommonFieldData(
PlacementData(Vector3.Zero, Vector3.Zero),
PlanetSideEmpire.TR,
0
),
255
)
case _ =>
ko
}
}
}
"Player" should {
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val obj : Player = {

View file

@ -0,0 +1,330 @@
// Copyright (c) 2017 PSForever
package objects
import akka.actor.{Actor, ActorRef, Props}
import base.ActorTest
import net.psforever.objects.ce.DeployedItem
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.{TurretDeployable, _}
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire}
import org.specs2.mutable.Specification
import scala.concurrent.duration._
class DeployableTest extends Specification {
"Deployable" should {
"know its owner by GUID" in {
val obj = new ExplosiveDeployable(GlobalDefinitions.he_mine)
obj.Owner mustEqual None
obj.Owner = PlanetSideGUID(10)
obj.Owner mustEqual Some(PlanetSideGUID(10))
}
"know its owner by GUID" in {
val obj = new ExplosiveDeployable(GlobalDefinitions.he_mine)
obj.OwnerName mustEqual None
obj.OwnerName = "TestCharacter"
obj.OwnerName mustEqual Some("TestCharacter")
}
"know its faction allegiance" in {
val obj = new ExplosiveDeployable(GlobalDefinitions.he_mine)
obj.Faction mustEqual PlanetSideEmpire.NEUTRAL
obj.Faction = PlanetSideEmpire.TR
obj.Faction mustEqual PlanetSideEmpire.TR
}
}
}
class SensorDeployableTest extends Specification {
"SensorDeployable" should {
"construct" in {
new SensorDeployable(GlobalDefinitions.motionalarmsensor)
ok
}
}
}
class ExplosiveDeployableTest extends Specification {
"ExplosiveDeployable" should {
"construct" in {
val obj = new ExplosiveDeployable(GlobalDefinitions.he_mine)
obj.Exploded mustEqual false
}
"explode" in {
val obj = new ExplosiveDeployable(GlobalDefinitions.he_mine)
obj.Exploded mustEqual false
obj.Exploded = true
obj.Exploded mustEqual true
}
}
}
class BoomerDeployableTest extends Specification {
"BoomerDeployable" should {
"construct" in {
val obj = new BoomerDeployable(GlobalDefinitions.boomer)
obj.Exploded mustEqual false
obj.Trigger mustEqual None
}
"explode" in {
val obj = new BoomerDeployable(GlobalDefinitions.boomer)
obj.Exploded mustEqual false
obj.Exploded = true
obj.Exploded mustEqual true
}
"manage its trigger" in {
val obj = new BoomerDeployable(GlobalDefinitions.boomer)
obj.Trigger mustEqual None
val trigger = new BoomerTrigger
obj.Trigger = trigger
obj.Trigger mustEqual Some(trigger)
obj.Trigger = None
obj.Trigger mustEqual None
}
}
}
class TrapDeployableTest extends Specification {
"SensorDeployable" should {
"construct" in {
val obj = new TrapDeployable(GlobalDefinitions.tank_traps)
obj.Health mustEqual GlobalDefinitions.tank_traps.MaxHealth
}
"update health values" in {
val obj = new TrapDeployable(GlobalDefinitions.tank_traps)
obj.Health mustEqual GlobalDefinitions.tank_traps.MaxHealth
obj.Health = 0
obj.Health mustEqual 0
}
}
}
class TurretDeployableTest extends Specification {
"TurretDeployable" should {
"define (valid turret objects)" in {
List(
DeployedItem.spitfire_turret.id, DeployedItem.spitfire_cloaked.id, DeployedItem.spitfire_aa.id,
DeployedItem.portable_manned_turret.id, DeployedItem.portable_manned_turret_tr.id,
DeployedItem.portable_manned_turret_nc.id, DeployedItem.portable_manned_turret_vs.id
).foreach(id => {
try { new TurretDeployableDefinition(id) } catch { case _ : Exception => ko }
})
ok
}
"define (invalid object)" in {
new TurretDeployableDefinition(5) must throwA[NoSuchElementException] //wrong object id altogether
}
"construct" in {
val obj = new TurretDeployable(GlobalDefinitions.spitfire_turret)
obj.Health mustEqual obj.MaxHealth
}
"update health values" in {
val obj = new TurretDeployable(GlobalDefinitions.spitfire_turret)
obj.Health mustEqual GlobalDefinitions.spitfire_turret.MaxHealth
obj.Health = 0
obj.Health mustEqual 0
}
"may have mount point" in {
new TurretDeployable(GlobalDefinitions.spitfire_turret).MountPoints mustEqual Map()
new TurretDeployable(GlobalDefinitions.portable_manned_turret_vs).MountPoints mustEqual Map(1 -> 0, 2 -> 0)
}
}
}
class ShieldGeneratorDeployableTest extends Specification {
"ShieldGeneratorDeployable" should {
"construct" in {
val obj = new ShieldGeneratorDeployable(GlobalDefinitions.deployable_shield_generator)
obj.Health mustEqual obj.MaxHealth
}
"update health values" in {
val obj = new ShieldGeneratorDeployable(GlobalDefinitions.deployable_shield_generator)
obj.Health mustEqual GlobalDefinitions.deployable_shield_generator.MaxHealth
obj.Health = 0
obj.Health mustEqual 0
}
}
}
class TurretControlConstructTest extends ActorTest {
"TurretControl" should {
"construct" in {
val obj = new TurretDeployable(GlobalDefinitions.spitfire_turret)
system.actorOf(Props(classOf[TurretControl], obj), s"${obj.Definition.Name}_test")
}
}
}
class TurretControlInitializeTest extends ActorTest {
"TurretControl" should {
"initialize" in {
val obj = new TurretDeployable(GlobalDefinitions.spitfire_turret)
obj.GUID = PlanetSideGUID(1)
assert(obj.Actor == ActorRef.noSender)
val init = system.actorOf(Props(classOf[DeployableTest.TurretInitializer], obj), "init_turret_test")
init ! "initialize"
expectNoMsg(200 milliseconds)
assert(obj.Actor != ActorRef.noSender)
}
}
}
class TurretControlUninitializeTest extends ActorTest {
"TurretControl" should {
"uninitialize" in {
val obj = new TurretDeployable(GlobalDefinitions.spitfire_turret)
val init = system.actorOf(Props(classOf[DeployableTest.TurretInitializer], obj), "init_turret_test")
obj.GUID = PlanetSideGUID(1)
init ! "initialize"
expectNoMsg(200 milliseconds)
assert(obj.Actor != ActorRef.noSender)
init ! "uninitialize"
expectNoMsg(200 milliseconds)
assert(obj.Actor == ActorRef.noSender)
}
}
}
class TurretControlMountTest extends ActorTest {
"TurretControl" should {
"control mounting" in {
val obj = new TurretDeployable(GlobalDefinitions.portable_manned_turret_tr) { GUID = PlanetSideGUID(1) }
obj.Faction = PlanetSideEmpire.TR
obj.Actor = system.actorOf(Props(classOf[TurretControl], obj), s"${obj.Definition.Name}_test")
assert(obj.Seats(0).Occupant.isEmpty)
val player1 = Player(Avatar("test1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
obj.Actor ! Mountable.TryMount(player1, 0)
val reply1a = receiveOne(200 milliseconds)
assert(reply1a.isInstanceOf[Mountable.MountMessages])
val reply1b = reply1a.asInstanceOf[Mountable.MountMessages]
assert(reply1b.player == player1)
assert(reply1b.response.isInstanceOf[Mountable.CanMount])
assert(obj.Seats(0).Occupant.contains(player1))
}
}
}
class TurretControlBlockMountTest extends ActorTest {
"TurretControl" should {
"block mounting by others if already mounted by someone" in {
val obj = new TurretDeployable(GlobalDefinitions.portable_manned_turret_tr) { GUID = PlanetSideGUID(1) }
obj.Faction = PlanetSideEmpire.TR
obj.Actor = system.actorOf(Props(classOf[TurretControl], obj), s"${obj.Definition.Name}_test")
assert(obj.Seats(0).Occupant.isEmpty)
val player1 = Player(Avatar("test1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
obj.Actor ! Mountable.TryMount(player1, 0)
val reply1a = receiveOne(200 milliseconds)
assert(reply1a.isInstanceOf[Mountable.MountMessages])
val reply1b = reply1a.asInstanceOf[Mountable.MountMessages]
assert(reply1b.player == player1)
assert(reply1b.response.isInstanceOf[Mountable.CanMount])
assert(obj.Seats(0).Occupant.contains(player1))
val player2 = Player(Avatar("test2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
obj.Actor ! Mountable.TryMount(player2, 0)
val reply2a = receiveOne(200 milliseconds)
assert(reply2a.isInstanceOf[Mountable.MountMessages])
val reply2b = reply2a.asInstanceOf[Mountable.MountMessages]
assert(reply2b.player == player2)
assert(reply2b.response.isInstanceOf[Mountable.CanNotMount])
}
}
}
class TurretControlBlockBetrayalMountTest extends ActorTest {
"TurretControl" should {
"block mounting by players of another faction" in {
val obj = new TurretDeployable(GlobalDefinitions.portable_manned_turret_tr) { GUID = PlanetSideGUID(1) }
obj.Faction = PlanetSideEmpire.TR
obj.Actor = system.actorOf(Props(classOf[TurretControl], obj), s"${obj.Definition.Name}_test")
assert(obj.Seats(0).Occupant.isEmpty)
val player = Player(Avatar("test", PlanetSideEmpire.VS, CharacterGender.Male, 0, CharacterVoice.Mute))
obj.Actor ! Mountable.TryMount(player, 0)
val reply1a = receiveOne(200 milliseconds)
assert(reply1a.isInstanceOf[Mountable.MountMessages])
val reply1b = reply1a.asInstanceOf[Mountable.MountMessages]
assert(reply1b.player == player)
assert(reply1b.response.isInstanceOf[Mountable.CanNotMount])
assert(obj.Seats(0).Occupant.isEmpty)
}
}
}
class TurretControlDismountTest extends ActorTest {
"TurretControl" should {
"control dismounting" in {
val obj = new TurretDeployable(GlobalDefinitions.portable_manned_turret_tr) { GUID = PlanetSideGUID(1) }
obj.Faction = PlanetSideEmpire.TR
obj.Actor = system.actorOf(Props(classOf[TurretControl], obj), s"${obj.Definition.Name}_test")
assert(obj.Seats(0).Occupant.isEmpty)
val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
obj.Actor ! Mountable.TryMount(player, 0)
val reply1a = receiveOne(200 milliseconds)
assert(reply1a.isInstanceOf[Mountable.MountMessages])
val reply1b = reply1a.asInstanceOf[Mountable.MountMessages]
assert(reply1b.player == player)
assert(reply1b.response.isInstanceOf[Mountable.CanMount])
assert(obj.Seats(0).Occupant.contains(player))
obj.Actor ! Mountable.TryDismount(player, 0)
val reply2a = receiveOne(200 milliseconds)
assert(reply2a.isInstanceOf[Mountable.MountMessages])
val reply2b = reply2a.asInstanceOf[Mountable.MountMessages]
assert(reply2b.player == player)
assert(reply2b.response.isInstanceOf[Mountable.CanDismount])
assert(obj.Seats(0).Occupant.isEmpty)
}
}
}
class TurretControlBetrayalMountTest extends ActorTest {
"TurretControl" should {
"allow all allegiances" in {
val obj = new TurretDeployable(
new TurretDeployableDefinition(685) { FactionLocked = false } //required (defaults to true)
) { GUID = PlanetSideGUID(1) }
obj.Faction = PlanetSideEmpire.TR
obj.Actor = system.actorOf(Props(classOf[TurretControl], obj), s"${obj.Definition.Name}_test")
assert(obj.Seats(0).Occupant.isEmpty)
val player = Player(Avatar("test", PlanetSideEmpire.NC, CharacterGender.Male, 0, CharacterVoice.Mute))
assert(player.Faction != obj.Faction)
obj.Actor ! Mountable.TryMount(player, 0)
val reply1a = receiveOne(200 milliseconds)
assert(reply1a.isInstanceOf[Mountable.MountMessages])
val reply1b = reply1a.asInstanceOf[Mountable.MountMessages]
assert(reply1b.player == player)
assert(reply1b.response.isInstanceOf[Mountable.CanMount])
assert(obj.Seats(0).Occupant.contains(player))
}
}
}
object DeployableTest {
class TurretInitializer(obj : TurretDeployable) extends Actor {
def receive : Receive = {
case "initialize" =>
obj.Definition.Initialize(obj, context)
case "uninitialize" =>
obj.Definition.Uninitialize(obj, context)
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -2,11 +2,13 @@
package objects
import net.psforever.objects._
import net.psforever.objects.equipment.CItem.{DeployedItem, Unit}
import net.psforever.objects.equipment._
import net.psforever.objects.inventory.InventoryTile
import net.psforever.objects.GlobalDefinitions._
import net.psforever.objects.ce.DeployedItem
import net.psforever.objects.definition._
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.CertificationType
import org.specs2.mutable._
class EquipmentTest extends Specification {
@ -329,62 +331,57 @@ class EquipmentTest extends Specification {
}
"ConstructionItem" should {
val advanced_ace_tr = ConstructionItemDefinition(39)
advanced_ace_tr.Modes += DeployedItem.tank_traps
advanced_ace_tr.Modes += DeployedItem.portable_manned_turret_tr
advanced_ace_tr.Modes += DeployedItem.deployable_shield_generator
advanced_ace_tr.Tile = InventoryTile.Tile63
"define" in {
val sample = ConstructionItemDefinition(Unit.advanced_ace)
sample.Modes += DeployedItem.tank_traps
sample.Modes += DeployedItem.portable_manned_turret_tr
sample.Modes += DeployedItem.deployable_shield_generator
sample.Tile = InventoryTile.Tile63
sample.Modes.head mustEqual DeployedItem.tank_traps
sample.Modes(1) mustEqual DeployedItem.portable_manned_turret_tr
sample.Modes(2) mustEqual DeployedItem.deployable_shield_generator
sample.Tile.Width mustEqual InventoryTile.Tile63.Width
sample.Tile.Height mustEqual InventoryTile.Tile63.Height
}
"construct" in {
val obj : ConstructionItem = ConstructionItem(advanced_ace_tr)
obj.Definition.ObjectId mustEqual advanced_ace_tr.ObjectId
val obj : ConstructionItem = ConstructionItem(GlobalDefinitions.ace)
obj.Definition.ObjectId mustEqual GlobalDefinitions.ace.ObjectId
}
"fire mode" in {
//explanation: router_telepad has one fire mode and that fire mode is our only option
val router_telepad : ConstructionItemDefinition = ConstructionItemDefinition(Unit.router_telepad)
router_telepad.Modes += DeployedItem.router_telepad_deployable
val obj : ConstructionItem = ConstructionItem(router_telepad)
//fmode = 0
obj.FireModeIndex mustEqual 0
obj.FireMode mustEqual DeployedItem.router_telepad_deployable
//fmode -> 1 (0)
obj.FireModeIndex = 1
obj.FireModeIndex mustEqual 0
obj.FireMode mustEqual DeployedItem.router_telepad_deployable
"fire modes" in {
val obj : ConstructionItem = ConstructionItem(GlobalDefinitions.ace)
obj.AmmoType mustEqual DeployedItem.boomer
obj.NextFireMode
obj.AmmoType mustEqual DeployedItem.he_mine
obj.NextFireMode
obj.AmmoType mustEqual DeployedItem.spitfire_turret
obj.NextFireMode
obj.AmmoType mustEqual DeployedItem.motionalarmsensor
obj.NextFireMode
obj.AmmoType mustEqual DeployedItem.boomer
}
"multiple fire modes" in {
//explanation: advanced_ace_tr has three fire modes; adjusting the FireMode changes between them
val obj : ConstructionItem = ConstructionItem(advanced_ace_tr)
//fmode = 0
obj.FireModeIndex mustEqual 0
obj.FireMode mustEqual DeployedItem.tank_traps
//fmode -> 1
"ammo types" in {
val obj : ConstructionItem = ConstructionItem(GlobalDefinitions.ace)
obj.NextFireMode
obj.FireModeIndex mustEqual 1
obj.FireMode mustEqual DeployedItem.portable_manned_turret_tr
//fmode -> 2
obj.AmmoType mustEqual DeployedItem.he_mine
obj.NextAmmoType
obj.AmmoType mustEqual DeployedItem.jammer_mine
obj.NextAmmoType
obj.AmmoType mustEqual DeployedItem.he_mine
}
"when switching fire modes, ammo mode resets to the first entry" in {
val obj : ConstructionItem = ConstructionItem(GlobalDefinitions.ace)
obj.NextFireMode
obj.FireModeIndex mustEqual 2
obj.FireMode mustEqual DeployedItem.deployable_shield_generator
//fmode -> 0
obj.AmmoType mustEqual DeployedItem.he_mine
obj.NextAmmoType
obj.AmmoType mustEqual DeployedItem.jammer_mine
obj.NextFireMode //spitfire_turret
obj.NextFireMode //motionalarmsensor
obj.NextFireMode //boomer
obj.NextFireMode
obj.FireModeIndex mustEqual 0
obj.FireMode mustEqual DeployedItem.tank_traps
obj.AmmoType mustEqual DeployedItem.he_mine
}
"qualify certifications that must be met before ammo types may be used" in {
val obj : ConstructionItem = ConstructionItem(GlobalDefinitions.ace)
obj.AmmoType mustEqual DeployedItem.boomer
obj.ModePermissions mustEqual Set(CertificationType.CombatEngineering)
obj.NextFireMode
obj.AmmoType mustEqual DeployedItem.he_mine
obj.ModePermissions mustEqual Set(CertificationType.CombatEngineering)
obj.NextAmmoType
obj.AmmoType mustEqual DeployedItem.jammer_mine
obj.ModePermissions mustEqual Set(CertificationType.AssaultEngineering)
}
}
@ -399,4 +396,21 @@ class EquipmentTest extends Specification {
obj.Definition.ObjectId mustEqual remote_electronics_kit.ObjectId
}
}
"BoomerTrigger" should {
"construct" in {
val obj : BoomerTrigger = new BoomerTrigger
obj.Definition.ObjectId mustEqual boomer_trigger.ObjectId
obj.Companion mustEqual None
}
"boomer trigger has a companion object referenced by GUID" in {
val obj : BoomerTrigger = new BoomerTrigger
obj.Companion mustEqual None
obj.Companion = PlanetSideGUID(1)
obj.Companion.contains(PlanetSideGUID(1)) mustEqual true
obj.Companion = None
obj.Companion mustEqual None
}
}
}

View file

@ -2,11 +2,8 @@
package objects
import net.psforever.objects._
import net.psforever.objects.equipment.CItem.{DeployedItem, Unit}
import net.psforever.objects.equipment._
import net.psforever.objects.inventory.InventoryTile
import net.psforever.objects.GlobalDefinitions._
import net.psforever.objects.definition._
import net.psforever.types.ExoSuitType
import org.specs2.mutable._

View file

@ -7,7 +7,7 @@ import net.psforever.objects.{Avatar, GlobalDefinitions, Player, Tool}
import net.psforever.objects.definition.ToolDefinition
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.structures.{Building, StructureType}
import net.psforever.objects.serverobject.turret.{MannedTurret, MannedTurretControl, MannedTurretDefinition, TurretUpgrade}
import net.psforever.objects.serverobject.turret._
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire}
@ -16,10 +16,10 @@ import org.specs2.mutable.Specification
import scala.collection.mutable
import scala.concurrent.duration._
class MannedTurretTest extends Specification {
"MannedTurretTest" should {
class FacilityTurretTest extends Specification {
"FacilityTurretTest" should {
"define" in {
val obj = new MannedTurretDefinition(480)
val obj = new TurretDefinition(480)
obj.Weapons mustEqual mutable.HashMap.empty[TurretUpgrade.Value, ToolDefinition]
obj.ReserveAmmunition mustEqual false
obj.FactionLocked mustEqual true
@ -28,7 +28,7 @@ class MannedTurretTest extends Specification {
}
"construct" in {
val obj = MannedTurret(GlobalDefinitions.manned_turret)
val obj = FacilityTurret(GlobalDefinitions.manned_turret)
obj.Weapons.size mustEqual 1
obj.Weapons(1).Equipment match {
case Some(tool : Tool) =>
@ -51,7 +51,7 @@ class MannedTurretTest extends Specification {
}
"upgrade to a different weapon" in {
val obj = MannedTurret(GlobalDefinitions.manned_turret)
val obj = FacilityTurret(GlobalDefinitions.manned_turret)
obj.Upgrade = TurretUpgrade.None
obj.Weapons(1).Equipment match {
case Some(tool : Tool) =>
@ -87,26 +87,26 @@ class MannedTurretTest extends Specification {
}
}
class MannedTurretControl1Test extends ActorTest {
"MannedTurretControl" should {
class FacilityTurretControl1Test extends ActorTest {
"FacilityTurretControl" should {
"construct" in {
val obj = MannedTurret(GlobalDefinitions.manned_turret)
obj.Actor = system.actorOf(Props(classOf[MannedTurretControl], obj), "turret-control")
val obj = FacilityTurret(GlobalDefinitions.manned_turret)
obj.Actor = system.actorOf(Props(classOf[FacilityTurretControl], obj), "turret-control")
assert(obj.Actor != ActorRef.noSender)
}
}
}
class MannedTurretControl2Test extends ActorTest {
class FacilityTurretControl2Test extends ActorTest {
val player = Player(Avatar("", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val obj = MannedTurret(GlobalDefinitions.manned_turret)
val obj = FacilityTurret(GlobalDefinitions.manned_turret)
obj.GUID = PlanetSideGUID(1)
obj.Actor = system.actorOf(Props(classOf[MannedTurretControl], obj), "turret-control")
obj.Actor = system.actorOf(Props(classOf[FacilityTurretControl], obj), "turret-control")
val bldg = Building(0, Zone.Nowhere, StructureType.Building)
bldg.Amenities = obj
bldg.Faction = PlanetSideEmpire.TR
"MannedTurretControl" should {
"FacilityTurretControl" should {
"seat on faction affiliation when FactionLock is true" in {
assert(player.Faction == PlanetSideEmpire.TR)
assert(obj.Faction == PlanetSideEmpire.TR)
@ -124,15 +124,15 @@ class MannedTurretControl2Test extends ActorTest {
}
}
class MannedTurretControl3Test extends ActorTest {
class FacilityTurretControl3Test extends ActorTest {
val player = Player(Avatar("", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val obj = MannedTurret(GlobalDefinitions.manned_turret)
val obj = FacilityTurret(GlobalDefinitions.manned_turret)
obj.GUID = PlanetSideGUID(1)
obj.Actor = system.actorOf(Props(classOf[MannedTurretControl], obj), "turret-control")
obj.Actor = system.actorOf(Props(classOf[FacilityTurretControl], obj), "turret-control")
val bldg = Building(0, Zone.Nowhere, StructureType.Building)
bldg.Amenities = obj
"MannedTurretControl" should {
"FacilityTurretControl" should {
"block seating on mismatched faction affiliation when FactionLock is true" in {
assert(player.Faction == PlanetSideEmpire.TR)
assert(obj.Faction == PlanetSideEmpire.NEUTRAL)
@ -150,17 +150,17 @@ class MannedTurretControl3Test extends ActorTest {
}
}
class MannedTurretControl4Test extends ActorTest {
class FacilityTurretControl4Test extends ActorTest {
val player = Player(Avatar("", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
val objDef = new MannedTurretDefinition(480)
val objDef = new TurretDefinition(480)
objDef.FactionLocked = false
val obj = MannedTurret(objDef)
val obj = FacilityTurret(objDef)
obj.GUID = PlanetSideGUID(1)
obj.Actor = system.actorOf(Props(classOf[MannedTurretControl], obj), "turret-control")
obj.Actor = system.actorOf(Props(classOf[FacilityTurretControl], obj), "turret-control")
val bldg = Building(0, Zone.Nowhere, StructureType.Building)
bldg.Amenities = obj
"MannedTurretControl" should {
"FacilityTurretControl" should {
"seating even with mismatched faction affiliation when FactionLock is false" in {
assert(player.Faction == PlanetSideEmpire.TR)
assert(obj.Faction == PlanetSideEmpire.NEUTRAL)

View file

@ -191,7 +191,7 @@ class ResourceSiloControlUpdate1Test extends ActorTest {
.actionMessage.asInstanceOf[AvatarAction.PlanetsideAttribute].attribute_value == 0)
val reply4 = probe1.receiveOne(500 milliseconds)
assert(obj.LowNtuWarningOn == false)
assert(!obj.LowNtuWarningOn)
assert(reply4.isInstanceOf[AvatarServiceMessage])
assert(reply4.asInstanceOf[AvatarServiceMessage].forChannel == "nowhere")
assert(reply4.asInstanceOf[AvatarServiceMessage]

View file

@ -246,20 +246,20 @@ class SpawnTubeObjectBuilderTest extends ActorTest {
}
}
class MannedTurretObjectBuilderTest extends ActorTest {
class FacilityTurretObjectBuilderTest extends ActorTest {
import net.psforever.objects.GlobalDefinitions.manned_turret
import net.psforever.objects.serverobject.turret.MannedTurret
"MannedTurretObjectBuilder" should {
import net.psforever.objects.serverobject.turret.FacilityTurret
"FacilityTurretObjectBuilder" should {
"build" in {
val hub = ServerObjectBuilderTest.NumberPoolHub
val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], ServerObjectBuilder(1,
MannedTurret.Constructor(manned_turret)), hub), "spawn-tube")
FacilityTurret.Constructor(manned_turret)), hub), "spawn-tube")
actor ! "!"
val reply = receiveOne(Duration.create(1000, "ms"))
assert(reply.isInstanceOf[MannedTurret])
assert(reply.asInstanceOf[MannedTurret].HasGUID)
assert(reply.asInstanceOf[MannedTurret].GUID == PlanetSideGUID(1))
assert(reply.isInstanceOf[FacilityTurret])
assert(reply.asInstanceOf[FacilityTurret].HasGUID)
assert(reply.asInstanceOf[FacilityTurret].GUID == PlanetSideGUID(1))
assert(reply == hub(1).get)
}
}

View file

@ -5,7 +5,7 @@ import base.ActorTest
import net.psforever.objects._
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
class GUIDTaskRegister2Test extends ActorTest {
class GUIDTaskRegisterAmmoTest extends ActorTest {
"RegisterEquipment -> RegisterObjectTask" in {
val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup
val obj = AmmoBox(GlobalDefinitions.energy_cell)

View file

@ -6,7 +6,7 @@ import net.psforever.objects._
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire}
class GUIDTaskRegister5Test extends ActorTest {
class GUIDTaskRegisterAvatarTest extends ActorTest {
"RegisterAvatar" in {
val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup
val obj = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))

View file

@ -4,7 +4,7 @@ package objects.guidtask
import base.ActorTest
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
class GUIDTaskRegister1Test extends ActorTest {
class GUIDTaskRegisterObjectTest extends ActorTest {
"RegisterObjectTask" in {
val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup
val obj = new GUIDTaskTest.TestObject

View file

@ -6,7 +6,7 @@ import net.psforever.objects._
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire}
class GUIDTaskRegister6Test extends ActorTest {
class GUIDTaskRegisterPlayerTest extends ActorTest {
"RegisterPlayer" in {
val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup
val obj = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))

View file

@ -5,7 +5,7 @@ import base.ActorTest
import net.psforever.objects._
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
class GUIDTaskRegister3Test extends ActorTest {
class GUIDTaskRegisterToolTest extends ActorTest {
"RegisterEquipment -> RegisterTool" in {
val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup
val obj = Tool(GlobalDefinitions.beamer)

View file

@ -0,0 +1,27 @@
// Copyright (c) 2017 PSForever
package objects.guidtask
import base.ActorTest
import net.psforever.objects._
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
class GUIDTaskRegisterTurretTest extends ActorTest {
"RegisterDeployableTurret" in {
val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup
val obj = new TurretDeployable(GlobalDefinitions.portable_manned_turret_vs)
val obj_wep = obj.Weapons(1).Equipment.get
val obj_ammo = obj_wep.asInstanceOf[Tool].AmmoSlot.Box
val obj_res = obj.Inventory.Items.map(_.obj)
assert(!obj.HasGUID)
assert(!obj_wep.HasGUID)
assert(!obj_ammo.HasGUID)
obj_res.foreach(box => !box.HasGUID)
taskResolver ! TaskResolver.GiveTask(new GUIDTaskTest.RegisterTestTask(probe.ref), List(GUIDTask.RegisterDeployableTurret(obj)(uns)))
probe.expectMsg(scala.util.Success)
assert(obj.HasGUID)
assert(obj_wep.HasGUID)
assert(obj_ammo.HasGUID)
obj_res.foreach(box => box.HasGUID)
}
}

View file

@ -5,7 +5,7 @@ import base.ActorTest
import net.psforever.objects._
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
class GUIDTaskRegister4Test extends ActorTest {
class GUIDTaskRegisterVehicleTest extends ActorTest {
"RegisterVehicle" in {
val (_, uns, taskResolver, probe) = GUIDTaskTest.CommonTestSetup
val obj = Vehicle(GlobalDefinitions.fury)

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