Yellow Ownership (#1226)

* just some tinkering and clean-up

* converted DeployItem from AvatarService to LocalService; attempt at resolving missing overwhip yellow ring is complicated; vehicle ownership packet wqorks on deployables that are mountable, but is less successful on normal simple deployables

* restoration of yellow ring of ownership around deployables; changes to variant of CommonFieldData transcorder used on certain deployable transcoders; static values are assigned parameter names and public variables are given types for completion

* initial packet for GenericObjectAction2Message and tests; repaired transcoders and tests for TRAP and small turrets

* force redraw of the whole boomer to assert reassignment of ownership; it's heavy-handed but it works

* deployable ownership should be asserted during both re-zoning and revival; refactoring of code in ZoningOperations
This commit is contained in:
Fate-JH 2024-08-22 23:33:47 -04:00 committed by GitHub
parent d2d7c2e09b
commit 9708bf9beb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
49 changed files with 593 additions and 502 deletions

View file

@ -130,29 +130,6 @@ class EquipmentInHandTest extends ActorTest {
}
}
class DeployItemTest extends ActorTest {
ServiceManager.boot(system)
val service = system.actorOf(Props(classOf[AvatarService], Zone.Nowhere), "deploy-item-test-service")
val objDef = GlobalDefinitions.motionalarmsensor
val obj = new SensorDeployable(objDef)
obj.Position = Vector3(1, 2, 3)
obj.Orientation = Vector3(4, 5, 6)
obj.GUID = PlanetSideGUID(40)
val pkt = ObjectCreateMessage(
objDef.ObjectId,
obj.GUID,
objDef.Packet.ConstructorData(obj).get
)
"AvatarService" should {
"pass DeployItem" in {
service ! Service.Join("test")
service ! AvatarServiceMessage("test", AvatarAction.DeployItem(PlanetSideGUID(10), obj))
expectMsg(AvatarServiceResponse("/test/Avatar", PlanetSideGUID(10), AvatarResponse.DropItem(pkt)))
}
}
}
class DroptItemTest extends ActorTest {
ServiceManager.boot(system)
val service = system.actorOf(Props(classOf[AvatarService], Zone.Nowhere), "release-test-service")

View file

@ -563,7 +563,7 @@ class GeneralOperations(
* @param unk2 na
*/
def hackObject(targetGuid: PlanetSideGUID, unk1: Long, unk2: HackState7): Unit = {
sendResponse(HackMessage(HackState1.Unk0, targetGuid, player_guid=Service.defaultPlayerGUID, progress=100, unk1, HackState.Hacked, unk2))
sendResponse(HackMessage(HackState1.Unk0, targetGuid, player_guid=Service.defaultPlayerGUID, progress=100, unk1.toFloat, HackState.Hacked, unk2))
}
/**

View file

@ -8,8 +8,9 @@ import akka.pattern.ask
import akka.util.Timeout
import net.psforever.actors.session.spectator.SpectatorMode
import net.psforever.login.WorldSession
import net.psforever.objects.avatar.BattleRank
import net.psforever.objects.avatar.{BattleRank, DeployableToolbox}
import net.psforever.objects.avatar.scoring.{CampaignStatistics, ScoreCard, SessionStatistics}
import net.psforever.objects.definition.converter.OCM
import net.psforever.objects.inventory.InventoryItem
import net.psforever.objects.serverobject.interior.Sidedness
import net.psforever.objects.serverobject.mount.Seat
@ -28,7 +29,7 @@ import scala.util.Success
//
import net.psforever.actors.session.{AvatarActor, SessionActor}
import net.psforever.login.WorldSession.RemoveOldEquipmentFromInventory
import net.psforever.objects.avatar.{Avatar, DeployableToolbox}
import net.psforever.objects.avatar.Avatar
import net.psforever.objects.avatar.{Award, AwardCategory, PlayerControl, Shortcut => AvatarShortcut}
import net.psforever.objects.ce.{Deployable, DeployableCategory, DeployedItem, TelepadLike}
import net.psforever.objects.definition.SpecialExoSuitDefinition
@ -215,104 +216,13 @@ class ZoningOperations(
sendResponse(ReplicationStreamMessage(5, Some(6), Vector.empty)) //clear squad list
sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 0)) // disable festive backpacks
//find and reclaim own deployables, if any
val foundDeployables = continent.DeployableList.filter {
case _: BoomerDeployable => false //if we do find boomers for any reason, ignore them
case dobj => dobj.OwnerName.contains(player.Name) && dobj.Health > 0
}
foundDeployables.collect {
case obj if avatar.deployables.AddOverLimit(obj) =>
obj.Actor ! Deployable.Ownership(player)
}
//render deployable objects
val (turrets, normal) = continent.DeployableList.partition(obj =>
DeployableToolbox.UnifiedType(obj.Definition.Item) == DeployedItem.portable_manned_turret
val deployables = continent.DeployableList
reclaimOurDeployables(deployables, name, manageDeployablesWith(player.GUID, avatar.deployables))
drawDeployableIconsOnMap(
depictDeployables(deployables).filter(_.Faction == faction)
)
normal.foreach(obj => {
val definition = obj.Definition
sendResponse(
ObjectCreateMessage(
definition.ObjectId,
obj.GUID,
definition.Packet.ConstructorData(obj).get
)
)
})
turrets.foreach(obj => {
val objGUID = obj.GUID
val definition = obj.Definition
sendResponse(
ObjectCreateMessage(
definition.ObjectId,
objGUID,
definition.Packet.ConstructorData(obj).get
)
)
//seated players
obj
.asInstanceOf[Mountable]
.Seats
.values
.map(_.occupant)
.collect {
case Some(occupant) =>
if (occupant.isAlive) {
val targetDefinition = occupant.avatar.definition
sendResponse(
ObjectCreateMessage(
targetDefinition.ObjectId,
occupant.GUID,
ObjectCreateMessageParent(objGUID, 0),
targetDefinition.Packet.ConstructorData(occupant).get
)
)
}
}
//auto turret behavior
(obj match {
case turret: AutomatedTurret with JammableUnit => turret.Target
case _ => None
}).collect {
target =>
val guid = obj.GUID
val turret = obj.asInstanceOf[AutomatedTurret]
sendResponse(ObjectDetectedMessage(guid, guid, 0, List(target.GUID)))
if (!obj.asInstanceOf[JammableUnit].Jammed) {
sendResponse(ChangeFireStateMessage_Start(turret.Weapons.values.head.Equipment.get.GUID))
}
}
})
//sensor animation
normal
.filter(obj =>
obj.Definition.DeployCategory == DeployableCategory.Sensors &&
!obj.Destroyed &&
(obj match {
case jObj: JammableUnit => !jObj.Jammed
case _ => true
})
)
.foreach(obj => {
sendResponse(TriggerEffectMessage(obj.GUID, "on", unk1=true, 1000))
})
//update the health of our faction's deployables (if necessary)
//draw our faction's deployables on the map
continent.DeployableList
.filter(obj => obj.Faction == faction && !obj.Destroyed)
.foreach(obj => {
if (obj.Health != obj.DefaultHealth) {
sendResponse(PlanetsideAttributeMessage(obj.GUID, 0, obj.Health))
}
val deployInfo = DeployableInfo(
obj.GUID,
Deployable.Icon(obj.Definition.Item),
obj.Position,
obj.OwnerGuid.getOrElse(PlanetSideGUID(0))
)
sendResponse(DeployableObjectsInfoMessage(DeploymentAction.Build, deployInfo))
})
//render Equipment that was dropped into zone before the player arrived
continent.EquipmentOnGround.foreach(item => {
continent.EquipmentOnGround.foreach { item =>
val definition = item.Definition
sendResponse(
ObjectCreateMessage(
@ -324,26 +234,19 @@ class ZoningOperations(
)
)
)
})
}
//load active players in zone (excepting players who are seated or players who are us)
val live = continent.LivePlayers
live
.filterNot(tplayer => {
.filterNot { tplayer =>
tplayer.GUID == player.GUID || tplayer.VehicleSeated.nonEmpty
})
.foreach(targetPlayer => {
val targetDefinition = player.avatar.definition
sendResponse(
ObjectCreateMessage(
targetDefinition.ObjectId,
targetPlayer.GUID,
targetDefinition.Packet.ConstructorData(targetPlayer).get
)
)
}
.foreach { targetPlayer =>
sendResponse(OCM.apply(targetPlayer))
if (targetPlayer.UsingSpecial == SpecialExoSuitDefinition.Mode.Anchored) {
sendResponse(PlanetsideAttributeMessage(targetPlayer.GUID, 19, 1))
}
})
}
//load corpses in zone
continent.Corpses.foreach {
spawn.DepictPlayerAsCorpse
@ -376,10 +279,7 @@ class ZoningOperations(
//active vehicles (and some wreckage)
vehicles.foreach { vehicle =>
val vguid = vehicle.GUID
val vdefinition = vehicle.Definition
sendResponse(
ObjectCreateMessage(vdefinition.ObjectId, vguid, vdefinition.Packet.ConstructorData(vehicle).get)
)
sendResponse(OCM.apply(vehicle))
//occupants other than driver (with exceptions)
vehicle.Seats
.filter {
@ -393,15 +293,10 @@ class ZoningOperations(
}
.foreach {
case (index, seat) =>
val targetPlayer = seat.occupant.get
val targetDefiniton = targetPlayer.avatar.definition
sendResponse(
ObjectCreateMessage(
targetDefiniton.ObjectId,
targetPlayer.GUID,
ObjectCreateMessageParent(vguid, index),
targetDefiniton.Packet.ConstructorData(targetPlayer).get
)
OCM.apply(seat.occupant.get)
.asInstanceOf[ObjectCreateMessage]
.copy(parentInfo = Some(ObjectCreateMessageParent(vguid, index)))
)
}
vehicle.SubsystemMessages().foreach { sendResponse }
@ -411,8 +306,8 @@ class ZoningOperations(
Vehicles.ReloadAccessPermissions(vehicle, player.Name)
}
//our vehicle would have already been loaded; see NewPlayerLoaded/AvatarCreate
usedVehicle.headOption match {
case Some(vehicle) =>
usedVehicle.headOption.collect {
case vehicle =>
//subsystems
vehicle.Actor ! Vehicle.UpdateSubsystemStates(player.Name, Some(false))
//depict any other passengers already in this zone
@ -430,15 +325,10 @@ class ZoningOperations(
}
.foreach {
case (index, seat) =>
val targetPlayer = seat.occupant.get
val targetDefinition = targetPlayer.avatar.definition
sendResponse(
ObjectCreateMessage(
targetDefinition.ObjectId,
targetPlayer.GUID,
ObjectCreateMessageParent(vguid, index),
targetDefinition.Packet.ConstructorData(targetPlayer).get
)
OCM.apply(seat.occupant.get)
.asInstanceOf[ObjectCreateMessage]
.copy(parentInfo = Some(ObjectCreateMessageParent(vguid, index)))
)
}
//since we would have only subscribed recently, we need to reload mount access states
@ -449,10 +339,9 @@ class ZoningOperations(
if (vehicle.Shields > 0) {
sendResponse(PlanetsideAttributeMessage(vguid, vehicle.Definition.shieldUiAttribute, vehicle.Shields))
}
case _ => () //no vehicle
}
//vehicle wreckages
wreckages.foreach(vehicle => {
wreckages.foreach { vehicle =>
sendResponse(
ObjectCreateMessage(
vehicle.Definition.DestroyedModel.get.id,
@ -460,17 +349,14 @@ class ZoningOperations(
DestroyedVehicleConverter.converter.ConstructorData(vehicle).get
)
)
})
}
//cargo occupants (including our own vehicle as cargo)
allActiveVehicles.collect {
case vehicle if vehicle.CargoHolds.nonEmpty =>
vehicle.CargoHolds.collect {
case (_index, hold: Cargo) if hold.isOccupied =>
CarrierBehavior.CargoMountBehaviorForAll(
vehicle,
hold.occupant.get,
_index
) //CargoMountBehaviorForUs can fail to attach the cargo vehicle on some clients
//CargoMountBehaviorForUs can fail to attach the cargo vehicle on some clients
CarrierBehavior.CargoMountBehaviorForAll(vehicle, hold.occupant.get, _index)
}
}
//special deploy states
@ -492,8 +378,8 @@ class ZoningOperations(
}
deployedVehicles.filter(_.Definition == GlobalDefinitions.router).foreach { obj =>
//the router won't work if it doesn't completely deploy
sendResponse(DeployRequestMessage(player.GUID, obj.GUID, DriveState.Deploying, 0, unk3=false, Vector3.Zero))
sendResponse(DeployRequestMessage(player.GUID, obj.GUID, DriveState.Deployed, 0, unk3=false, Vector3.Zero))
sendResponse(DeployRequestMessage(player.GUID, obj.GUID, DriveState.Deploying, 0, unk3 = false, Vector3.Zero))
sendResponse(DeployRequestMessage(player.GUID, obj.GUID, DriveState.Deployed, 0, unk3 = false, Vector3.Zero))
sessionLogic.general.toggleTeleportSystem(obj, TelepadLike.AppraiseTeleportationSystem(obj, continent))
}
ServiceManager.serviceManager
@ -504,41 +390,30 @@ class ZoningOperations(
case _ =>
}
//implant terminals
continent.map.terminalToInterface.foreach({
continent.map.terminalToInterface.foreach {
case (terminal_guid, interface_guid) =>
val parent_guid = PlanetSideGUID(terminal_guid)
continent.GUID(interface_guid) match {
case Some(obj: Terminal) =>
val objDef = obj.Definition
continent.GUID(interface_guid).collect {
case obj: Terminal =>
sendResponse(
ObjectCreateMessage(
objDef.ObjectId,
PlanetSideGUID(interface_guid),
ObjectCreateMessageParent(parent_guid, 1),
objDef.Packet.ConstructorData(obj).get
)
OCM.apply(obj)
.asInstanceOf[ObjectCreateMessage]
.copy(parentInfo = Some(ObjectCreateMessageParent(parent_guid, 1)))
)
case _ => ()
}
//mount terminal occupants
continent.GUID(terminal_guid) match {
case Some(obj: Mountable) =>
obj.Seats(0).occupant match {
case Some(targetPlayer: Player) =>
val targetDefinition = targetPlayer.avatar.definition
continent.GUID(terminal_guid).collect {
case obj: Mountable =>
obj.Seats(0).occupant.collect {
case occupant: Player =>
sendResponse(
ObjectCreateMessage(
targetDefinition.ObjectId,
targetPlayer.GUID,
ObjectCreateMessageParent(parent_guid, 0),
targetDefinition.Packet.ConstructorData(targetPlayer).get
)
OCM.apply(occupant)
.asInstanceOf[ObjectCreateMessage]
.copy(parentInfo = Some(ObjectCreateMessageParent(parent_guid, 0)))
)
case _ => ()
}
case _ => ()
}
})
}
//facility turrets
continent.map.turretToWeapon
.map { case (turret_guid: Int, _) => continent.GUID(turret_guid) }
@ -549,14 +424,10 @@ class ZoningOperations(
if (!turret.isUpgrading) {
turret.ControlledWeapon(wepNumber = 1).foreach {
case obj: Tool =>
val objDef = obj.Definition
sendResponse(
ObjectCreateMessage(
objDef.ObjectId,
obj.GUID,
ObjectCreateMessageParent(pguid, 1),
objDef.Packet.ConstructorData(obj).get
)
OCM.apply(obj)
.asInstanceOf[ObjectCreateMessage]
.copy(parentInfo = Some(ObjectCreateMessageParent(pguid, 1)))
)
case _ => ()
}
@ -564,38 +435,19 @@ class ZoningOperations(
//reserved ammunition?
//TODO need to register if it exists
//mount turret occupant
turret.Seats(0).occupant match {
case Some(targetPlayer: Player) =>
val targetDefinition = targetPlayer.avatar.definition
turret.Seats(0).occupant.collect {
case occupant: Player =>
sendResponse(
ObjectCreateMessage(
targetDefinition.ObjectId,
targetPlayer.GUID,
ObjectCreateMessageParent(pguid, 0),
targetDefinition.Packet.ConstructorData(targetPlayer).get
)
OCM.apply(occupant)
.asInstanceOf[ObjectCreateMessage]
.copy(parentInfo = Some(ObjectCreateMessageParent(pguid, 0)))
)
case _ => ()
}
turret.Target.collect {
target =>
val guid = turret.GUID
sendResponse(ObjectDetectedMessage(guid, guid, 0, List(target.GUID)))
if (!turret.Jammed) {
sendResponse(ChangeFireStateMessage_Start(turret.Weapons.values.head.Equipment.get.GUID))
}
}
triggerAutomatedTurretFire(turret)
}
//remote projectiles and radiation clouds
continent.Projectiles.foreach { projectile =>
val definition = projectile.Definition
sendResponse(
ObjectCreateMessage(
definition.ObjectId,
projectile.GUID,
definition.Packet.ConstructorData(projectile).get
)
)
sendResponse(OCM.apply(projectile))
}
//spawn point update request
continent.VehicleEvents ! VehicleServiceMessage(
@ -1166,14 +1018,7 @@ class ZoningOperations(
// sync capture flags
case llu: CaptureFlag =>
// Create LLU
sendResponse(
ObjectCreateMessage(
llu.Definition.ObjectId,
llu.GUID,
llu.Definition.Packet.ConstructorData(llu).get
)
)
sendResponse(OCM.apply(llu))
// Attach it to a player if it has a carrier
if (llu.Carrier.nonEmpty) {
continent.LocalEvents ! LocalServiceMessage(
@ -1755,6 +1600,188 @@ class ZoningOperations(
*/
def NormalKeepAlive(): Unit = {}
/**
* Find all deployables that internally keep track of a player's name as its owner
* and change the GUID associated with that owner (reclaim it).
* @param deployables list of deployables
* @param name owner name
* @param reclamationStrategy what happens to deployables to re-assign ownership
* @return the list of deployables whose owenrship has been re-assigned
*/
def reclaimOurDeployables(
deployables: List[Deployable],
name: String,
reclamationStrategy: Deployable => Option[Deployable]
): List[Deployable] = {
deployables
.filter {
case _: BoomerDeployable => false //always ignore
case obj => obj.OwnerName.contains(name) && !obj.Destroyed && obj.Health > 0
}
.flatMap(reclamationStrategy)
}
/**
* If the deployable was added to a management collection, reassign its internal owner GUID.
* Hence, it can fail.
* @param toGuid anticipated ownership GUID
* @param managedDeployables collection for logically organizing deployables
* @param obj deployable designated for ownership
* @return the deployable, if it was capable of being managed and ownership was re-assigned
*/
def manageDeployablesWith(toGuid: PlanetSideGUID, managedDeployables: DeployableToolbox)(obj: Deployable): Option[Deployable] = {
if (managedDeployables.AddOverLimit(obj)) {
reassignDeployablesTo(toGuid)(obj)
} else {
None
}
}
/**
* Reassign the internal owner GUID on this deployable.
* @param toGuid anticipated ownership GUID
* @param obj deployable designated for ownership
* @return the deployable, but now assigned to the GUID
*/
def reassignDeployablesTo(toGuid: PlanetSideGUID)(obj: Deployable): Option[Deployable] = {
obj.OwnerGuid = toGuid
Some(obj)
}
/**
* Render and animate all provided deployables.
* Animation includes occupants for mountable deployables and ongoing behaviors for automated deployables.
* @param deployables list of deployables
* @return list of working deployables
* @see `OCM.apply`
*/
def depictDeployables(deployables: List[Deployable]): List[Deployable] = {
val (smallTurrets, largeTurrets, sensors, normal, brokenThings) = {
val (broken, working) = deployables.partition { obj =>
obj.Destroyed || obj.Health == 0 || (obj match {
case jammable: JammableUnit => jammable.Jammed
case _ => false
})
}
val (small, remainder1) = working.partition { obj => obj.Definition.DeployCategory == DeployableCategory.SmallTurrets }
val (large, remainder2) = remainder1.partition { obj => obj.Definition.DeployCategory == DeployableCategory.FieldTurrets }
val (sensor, remainder3) = remainder2.partition { obj => obj.Definition.DeployCategory == DeployableCategory.Sensors }
(small, large, sensor, remainder3, broken)
}
val miscThings = normal ++ sensors ++ smallTurrets
(brokenThings ++ miscThings).foreach { obj =>
sendResponse(OCM.apply(obj))
}
largeTurrets.foreach { obj =>
sendResponse(OCM.apply(obj))
//seated players
obj
.asInstanceOf[Mountable]
.Seats
.values
.map(_.occupant)
.collect {
case Some(occupant) if occupant.isAlive =>
sendResponse(
OCM.apply(occupant)
.asInstanceOf[ObjectCreateMessage]
.copy(parentInfo = Some(ObjectCreateMessageParent(obj.GUID, 0)))
)
}
}
triggerAutomatedTurretFire(smallTurrets)
triggerSensorDeployables(sensors)
miscThings ++ largeTurrets
}
/**
* Render and animate all provided deployables.
* Animation includes ongoing behaviors for automated deployables.
* @param deployables list of deployables
* @return list of working deployables
* @see `OCM.apply`
*/
def depictDeployablesUponRevival(deployables: List[Deployable]): List[Deployable] = {
val (smallTurrets, sensors, normal) = {
val (_, working) = deployables.partition { obj =>
obj.Destroyed || obj.Health == 0 || (obj match {
case jammable: JammableUnit => jammable.Jammed
case _ => false
})
}
val (small, remainder1) = working.partition { obj => obj.Definition.DeployCategory == DeployableCategory.SmallTurrets }
val (sensor, remainder2) = remainder1.partition { obj => obj.Definition.DeployCategory == DeployableCategory.Sensors }
(small, sensor, remainder2)
}
val miscThings = normal ++ sensors ++ smallTurrets
miscThings.foreach { obj =>
sendResponse(OCM.apply(obj))
}
triggerAutomatedTurretFire(smallTurrets)
triggerSensorDeployables(sensors)
miscThings
}
/**
* Treat the deployables as sensor-types and provide appropriate animation.
* This animation is the glowing halo-ing effect on its sensor bulb.
* @param sensors list of deployables
* @see `TriggerEffectMessage`
*/
def triggerSensorDeployables(sensors: List[Deployable]): Unit = {
sensors.foreach { obj =>
sendResponse(TriggerEffectMessage(obj.GUID, effect = "on", unk1 = true, unk2 = 1000))
}
}
/**
* Treat the deployables as small turret-types and provide appropriate animation.
* This animation is related to its automation - tracking and shooting.
* @param turrets list of deployables
*/
def triggerAutomatedTurretFire(turrets: List[Deployable]): Unit = {
turrets.collect { case turret: AutomatedTurret =>
triggerAutomatedTurretFire(turret)
}
}
/**
* Provide appropriate animation to the small turret deployable.
* This animation is related to its automation - tracking and shooting.
* @param turret small turret deployable
* @see `ChangeFireStateMessage_Start`
* @see `ObjectDetectedMessage`
*/
def triggerAutomatedTurretFire(turret: AutomatedTurret): Unit = {
turret.Target.foreach { target =>
val guid = turret.GUID
sendResponse(ObjectDetectedMessage(guid, guid, 0, List(target.GUID)))
sendResponse(ChangeFireStateMessage_Start(turret.Weapons.values.head.Equipment.get.GUID))
}
}
/**
* Draw or redraw deployment map icons related to deployable presence and deployable management (if the owner).
* Assert deployable health as a precaution.
* @param deployables list of deployables
* @see `DeployableObjectsInfoMessage`
*/
def drawDeployableIconsOnMap(deployables: List[Deployable]): Unit = {
deployables
.foreach { obj =>
val guid = obj.GUID
val health = obj.Health
if (health != obj.DefaultHealth) {
sendResponse(PlanetsideAttributeMessage(guid, 0, health))
}
sendResponse(DeployableObjectsInfoMessage(DeploymentAction.Build, DeployableInfo(
guid,
Deployable.Icon(obj.Definition.Item),
obj.Position,
obj.OwnerGuid.getOrElse(Service.defaultPlayerGUID)
)))
}
}
/* nested class - spawn operations */
class SpawnOperations() {
@ -2298,10 +2325,11 @@ class ZoningOperations(
//vehicle and driver/passenger
interstellarFerry = None
val vdef = vehicle.Definition
val vObjectId = vdef.ObjectId
val vguid = vehicle.GUID
vehicle.Position = shiftPosition.getOrElse(vehicle.Position)
vehicle.Orientation = shiftOrientation.getOrElse(vehicle.Orientation)
val vdata = if (seat == 0) {
if (seat == 0) {
//driver
if (vehicle.Zone ne continent) {
continent.Transport ! Zone.Vehicle.Spawn(vehicle)
@ -2311,7 +2339,7 @@ class ZoningOperations(
mount.unmount(player)
player.VehicleSeated = None
val data = vdef.Packet.ConstructorData(vehicle).get
sendResponse(ObjectCreateMessage(vehicle.Definition.ObjectId, vguid, data))
sendResponse(ObjectCreateMessage(vObjectId, vguid, data))
mount.mount(player)
player.VehicleSeated = vguid
Vehicles.Own(vehicle, player)
@ -2320,7 +2348,7 @@ class ZoningOperations(
.foreach { _.MountedIn = vguid }
events ! VehicleServiceMessage(
zoneid,
VehicleAction.LoadVehicle(player.GUID, vehicle, vdef.ObjectId, vguid, data)
VehicleAction.LoadVehicle(player.GUID, vehicle, vObjectId, vguid, data)
)
carrierInfo match {
case (Some(carrier), Some((index, _))) =>
@ -2329,18 +2357,15 @@ class ZoningOperations(
vehicle.MountedIn = None
}
vehicle.allowInteraction = true
data
} else {
//passenger
//non-drivers are not rendered in the vehicle at this time
val data = vdef.Packet.ConstructorData(vehicle).get
sendResponse(ObjectCreateMessage(vehicle.Definition.ObjectId, vguid, data))
sendResponse(OCM.apply(vehicle))
carrierInfo match {
case (Some(carrier), Some((index, _))) =>
CargoMountBehaviorForUs(carrier, vehicle, index)
case _ => ()
}
data
}
val originalSeated = player.VehicleSeated
player.VehicleSeated = vguid
@ -2357,25 +2382,26 @@ class ZoningOperations(
)
}
Vehicles.ReloadAccessPermissions(vehicle, player.Name)
log.debug(s"AvatarCreate (vehicle): ${player.Name}'s ${vehicle.Definition.Name}")
log.trace(s"AvatarCreate (vehicle): ${player.Name}'s ${vehicle.Definition.Name} - $vguid -> $vdata")
log.debug(s"AvatarCreate (vehicle): ${player.Name}'s ${vdef.Name}")
AvatarCreateInVehicle(player, vehicle, seat)
case _ =>
player.VehicleSeated = None
val packet = player.avatar.definition.Packet
val data = packet.DetailedConstructorData(player).get
val guid = player.GUID
sendResponse(ObjectCreateDetailedMessage(ObjectClass.avatar, guid, data))
val definition = player.avatar.definition
val guid = player.GUID
sendResponse(OCM.detailed(player))
continent.AvatarEvents ! AvatarServiceMessage(
zoneid,
AvatarAction.LoadPlayer(guid, ObjectClass.avatar, guid, packet.ConstructorData(player).get, None)
AvatarAction.LoadPlayer(guid, definition.ObjectId, guid, definition.Packet.ConstructorData(player).get, None)
)
log.debug(s"AvatarCreate: ${player.Name}")
log.trace(s"AvatarCreate: ${player.Name} - $guid -> $data")
}
continent.Population ! Zone.Population.Spawn(avatar, player, avatarActor)
avatarActor ! AvatarActor.RefreshPurchaseTimes()
drawDeployableIconsOnMap(
depictDeployablesUponRevival(
reclaimOurDeployables(continent.DeployableList, player.Name, reassignDeployablesTo(player.GUID))
)
)
//begin looking for conditions to set the avatar
context.system.scheduler.scheduleOnce(delay = 250 millisecond, context.self, SessionActor.SetCurrentAvatar(player, 200))
}
@ -2403,11 +2429,9 @@ class ZoningOperations(
val pguid = tplayer.GUID
val vguid = vehicle.GUID
tplayer.VehicleSeated = None
val pdata = pdef.Packet.DetailedConstructorData(tplayer).get
tplayer.VehicleSeated = vguid
log.debug(s"AvatarCreateInVehicle: ${player.Name}")
log.trace(s"AvatarCreateInVehicle: ${player.Name} - $pguid -> $pdata")
sendResponse(ObjectCreateDetailedMessage(pdef.ObjectId, pguid, pdata))
sendResponse(OCM.detailed(tplayer))
if (seat == 0 || vehicle.WeaponControlledFromSeat(seat).nonEmpty) {
sendResponse(ObjectAttachMessage(vguid, pguid, seat))
sessionLogic.general.accessContainer(vehicle)
@ -2459,13 +2483,10 @@ class ZoningOperations(
sessionLogic.vehicles.GetKnownVehicleAndSeat() match {
case (Some(vehicle: Vehicle), Some(seat: Int)) =>
//vehicle and driver/passenger
val vdef = vehicle.Definition
val vguid = vehicle.GUID
val vdata = vdef.Packet.ConstructorData(vehicle).get
sendResponse(ObjectCreateMessage(vehicle.Definition.ObjectId, vguid, vdata))
sendResponse(OCM.apply(vehicle))
Vehicles.ReloadAccessPermissions(vehicle, continent.id)
log.debug(s"AvatarCreate (vehicle): ${player.Name}'s ${vehicle.Definition.Name}")
log.trace(s"AvatarCreate (vehicle): ${player.Name}'s ${vehicle.Definition.Name} - $vguid -> $vdata")
val pdef = player.avatar.definition
val pguid = player.GUID
player.VehicleSeated = None

View file

@ -13,6 +13,7 @@ import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.objects.zones.Zone
import net.psforever.services.Service
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import net.psforever.types.PlanetSideEmpire
import scala.annotation.unused
@ -81,8 +82,14 @@ class BoomerDeployableControl(mine: BoomerDeployable)
}
override def gainOwnership(player: Player): Unit = {
mine.Faction = PlanetSideEmpire.NEUTRAL //force map icon redraw
val originalOwner = mine.OwnerName
super.gainOwnership(player, player.Faction)
val events = mine.Zone.LocalEvents
val msg = LocalAction.DeployItem(mine)
originalOwner.collect { name =>
events ! LocalServiceMessage(name, msg)
}
events ! LocalServiceMessage(player.Name, msg)
}
override def dismissDeployable() : Unit = {

View file

@ -18,16 +18,11 @@ trait OwnableByPlayer {
def OwnerGuid_=(owner: Player): Option[PlanetSideGUID] = OwnerGuid_=(Some(owner.GUID))
def OwnerGuid_=(owner: Option[PlanetSideGUID]): Option[PlanetSideGUID] = {
owner match {
case Some(_) =>
ownerGuid = owner
case None =>
ownerGuid = None
}
ownerGuid = owner
OwnerGuid
}
def OwnerName: Option[String] = owner.map { _.name }
def OwnerName: Option[String] = owner.map(_.name)
def OriginalOwnerName: Option[String] = originalOwnerName
@ -47,7 +42,7 @@ trait OwnableByPlayer {
(originalOwnerName, playerOpt) match {
case (None, Some(player)) =>
owner = Some(UniquePlayer(player))
originalOwnerName = originalOwnerName.orElse { Some(player.Name) }
originalOwnerName = originalOwnerName.orElse(Some(player.Name))
OwnerGuid = player
case (_, Some(player)) =>
owner = Some(UniquePlayer(player))

View file

@ -524,7 +524,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
log.warn(s"${player.Name} failed to pick up an item ($item_guid) from the ground because $reason")
case Player.BuildDeployable(obj: TelepadDeployable, tool: Telepad) =>
obj.Router = tool.Router //necessary; forwards link to the router that prodcued the telepad
obj.Router = tool.Router //necessary; forwards link to the router that produced the telepad
setupDeployable(obj, tool)
case Player.BuildDeployable(obj, tool) =>
@ -534,7 +534,6 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
deployablePair match {
case Some((deployable, tool)) if deployable eq obj =>
val zone = player.Zone
//boomers
val trigger = new BoomerTrigger
trigger.Companion = obj.GUID
obj.Trigger = trigger
@ -552,7 +551,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
TaskWorkflow.execute(PutNewEquipmentInInventoryOrDrop(player)(trigger))
}
Players.buildCooldownReset(zone, player.Name, obj)
case _ => ;
case _ => ()
}
deployablePair = None
@ -569,7 +568,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
TelepadControl.TelepadError(zone, player.Name, msg = "@Telepad_NoDeploy_RouterLost")
}
Players.buildCooldownReset(zone, player.Name, obj)
case _ => ;
case _ => ()
}
deployablePair = None
@ -584,7 +583,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
case None =>
log.warn(s"${player.Name} should have destroyed a ${tool.Definition.Name} here, but could not find it")
}
case _ => ;
case _ => ()
}
deployablePair = None
@ -701,16 +700,27 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
if (deployables.Valid(obj) &&
!deployables.Contains(obj) &&
Players.deployableWithinBuildLimits(player, obj)) {
//deployables, upon construction, may display an animation effect
tool.Definition match {
case GlobalDefinitions.ace | /* animation handled in deployable lifecycle */
GlobalDefinitions.router_telepad => ; /* no special animation */
case GlobalDefinitions.router_telepad => () /* no special animation */
case GlobalDefinitions.ace
if obj.Definition.deployAnimation == DeployAnimation.Standard =>
zone.LocalEvents ! LocalServiceMessage(
zone.id,
LocalAction.TriggerEffectLocation(
obj.OwnerGuid.getOrElse(Service.defaultPlayerGUID),
"spawn_object_effect",
obj.Position,
obj.Orientation
)
)
case GlobalDefinitions.advanced_ace
if obj.Definition.deployAnimation == DeployAnimation.Fdu =>
zone.AvatarEvents ! AvatarServiceMessage(zone.id, AvatarAction.PutDownFDU(player.GUID))
case _ =>
org.log4s.getLogger(name = "Deployables").warn(
s"not sure what kind of construction item to animate - ${tool.Definition.Name}"
)
org.log4s
.getLogger(name = "Deployables")
.warn(s"not sure what kind of construction item to animate - ${tool.Definition.Name}")
}
deployablePair = Some((obj, tool))
obj.Faction = player.Faction
@ -1158,10 +1168,14 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
zone.GUID(trigger.Companion) match {
case Some(obj: BoomerDeployable) =>
val deployables = player.avatar.deployables
if (deployables.Valid(obj)) {
if (!deployables.Contains(obj) && deployables.Valid(obj)) {
events ! AvatarServiceMessage(toChannel, AvatarAction.SendResponse(
Service.defaultPlayerGUID,
GenericObjectAction2Message(1, player.GUID, trigger.GUID)
))
Players.gainDeployableOwnership(player, obj, deployables.AddOverLimit)
}
case _ => ;
case _ => ()
}
case citem: ConstructionItem
@ -1172,7 +1186,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
}
Deployables.initializeConstructionItem(player.avatar.certifications, citem)
case _ => ;
case _ => ()
}
events ! AvatarServiceMessage(
toChannel,

View file

@ -4,7 +4,6 @@ package net.psforever.objects.ce
import akka.actor.{Actor, ActorRef, Cancellable}
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
import net.psforever.objects._
import net.psforever.objects.definition.DeployAnimation
import net.psforever.objects.zones.Zone
import net.psforever.packet.game._
import net.psforever.services.Service
@ -75,7 +74,7 @@ trait DeployableBehavior {
}
case Deployable.Ownership(Some(player))
if !DeployableObject.Destroyed && DeployableObject.OwnerGuid.isEmpty =>
if !DeployableObject.Destroyed /*&& DeployableObject.OwnerGuid.isEmpty*/ =>
if (constructed.contains(true)) {
gainOwnership(player)
} else {
@ -105,6 +104,7 @@ trait DeployableBehavior {
def loseOwnership(obj: Deployable, toFaction: PlanetSideEmpire.Value): Unit = {
DeployableBehavior.changeOwnership(
obj,
toOwner = "",
toFaction,
DeployableInfo(obj.GUID, Deployable.Icon.apply(obj.Definition.Item), obj.Position, Service.defaultPlayerGUID)
)
@ -138,18 +138,18 @@ trait DeployableBehavior {
*/
def gainOwnership(player: Player, toFaction: PlanetSideEmpire.Value): Unit = {
val obj = DeployableObject
obj.AssignOwnership(player)
decay.cancel()
DeployableBehavior.changeOwnership(
obj,
player.Name,
toFaction,
DeployableInfo(obj.GUID, Deployable.Icon.apply(obj.Definition.Item), obj.Position, obj.OwnerGuid.get)
DeployableInfo(obj.GUID, Deployable.Icon.apply(obj.Definition.Item), obj.Position, player.GUID)
)
obj.AssignOwnership(player)
}
/**
* The first stage of the deployable build process, to put the formal process in motion.
* Deployables, upon construction, may display an animation effect.
* Parameters are required to be passed onto the next stage of the build process and are not used here.
* @see `DeployableDefinition.deployAnimation`
* @see `DeployableDefinition.DeployTime`
@ -157,21 +157,9 @@ trait DeployableBehavior {
* @param callback an `ActorRef` used for confirming the deployable's completion of the process
*/
def setupDeployable(callback: ActorRef): Unit = {
import scala.concurrent.ExecutionContext.Implicits.global
val obj = DeployableObject
constructed = Some(false)
if (obj.Definition.deployAnimation == DeployAnimation.Standard) {
val zone = obj.Zone
zone.LocalEvents ! LocalServiceMessage(
zone.id,
LocalAction.TriggerEffectLocation(
obj.OwnerGuid.getOrElse(Service.defaultPlayerGUID),
"spawn_object_effect",
obj.Position,
obj.Orientation
)
)
}
import scala.concurrent.ExecutionContext.Implicits.global
setup = context.system.scheduler.scheduleOnce(
obj.Definition.DeployTime milliseconds,
self,
@ -185,7 +173,7 @@ trait DeployableBehavior {
* Nothing dangerous happens if it does not begin to decay, but, because it is not under a player's management,
* the deployable will not properly transition to a decay state for another reason
* and can linger in the zone ownerless for as long as it is not destroyed.
* @see `AvatarAction.DeployItem`
* @see `LocalAction.DeployItem`
* @see `DeploymentAction`
* @see `DeployableInfo`
* @see `LocalAction.DeployableMapIcon`
@ -197,27 +185,22 @@ trait DeployableBehavior {
setup = Default.Cancellable
constructed = Some(true)
val obj = DeployableObject
val zone = obj.Zone
val zone = obj.Zone
val localEvents = zone.LocalEvents
val owner = obj.OwnerGuid.getOrElse(Service.defaultPlayerGUID)
obj.OwnerName match {
case Some(_) =>
case None =>
import scala.concurrent.ExecutionContext.Implicits.global
decay = context.system.scheduler.scheduleOnce(
Deployable.decay,
self,
Deployable.Deconstruct()
)
obj.OwnerName.orElse {
import scala.concurrent.ExecutionContext.Implicits.global
decay = context.system.scheduler.scheduleOnce(Deployable.decay, self, Deployable.Deconstruct())
None
}
//zone build
zone.AvatarEvents ! AvatarServiceMessage(zone.id, AvatarAction.DeployItem(Service.defaultPlayerGUID, obj))
localEvents ! LocalServiceMessage(zone.id, LocalAction.DeployItem(obj))
//zone map icon
localEvents ! LocalServiceMessage(
obj.Faction.toString,
LocalAction.DeployableMapIcon(
Service.defaultPlayerGUID,
DeploymentAction.Build, DeployableInfo(obj.GUID, Deployable.Icon(obj.Definition.Item), obj.Position, owner)
DeploymentAction.Build,
DeployableInfo(obj.GUID, Deployable.Icon(obj.Definition.Item), obj.Position, obj.OwnerGuid.getOrElse(Service.defaultPlayerGUID))
)
)
//local build management
@ -290,28 +273,28 @@ object DeployableBehavior {
* @param toFaction na
* @param info na
*/
def changeOwnership(obj: Deployable, toFaction: PlanetSideEmpire.Value, info: DeployableInfo): Unit = {
def changeOwnership(obj: Deployable, toOwner: String, toFaction: PlanetSideEmpire.Value, info: DeployableInfo): Unit = {
val dGuid = obj.GUID
val originalFaction = obj.Faction
val zone = obj.Zone
val localEvents = zone.LocalEvents
if (originalFaction != toFaction) {
val guid = obj.GUID
val zone = obj.Zone
val localEvents = zone.LocalEvents
obj.Faction = toFaction
//visual tells in regards to ownership by faction
zone.AvatarEvents ! AvatarServiceMessage(
zone.id,
AvatarAction.SetEmpire(Service.defaultPlayerGUID, guid, toFaction)
AvatarAction.SetEmpire(Service.defaultPlayerGUID, dGuid, toFaction)
)
//remove knowledge by the previous owner's faction
localEvents ! LocalServiceMessage(
originalFaction.toString,
LocalAction.DeployableMapIcon(Service.defaultPlayerGUID, DeploymentAction.Dismiss, info)
)
//display to the given faction
localEvents ! LocalServiceMessage(
toFaction.toString,
LocalAction.DeployableMapIcon(Service.defaultPlayerGUID, DeploymentAction.Build, info)
)
}
//display to the given faction
localEvents ! LocalServiceMessage(
toFaction.toString,
LocalAction.DeployableMapIcon(Service.defaultPlayerGUID, DeploymentAction.Build, info)
)
}
}

View file

@ -24,7 +24,7 @@ class SmallTurretConverter extends ObjectCreateConverter[TurretDeployable]() {
v1 = true,
None,
obj.Jammed,
Some(true),
None,
None,
obj.OwnerGuid match {
case Some(owner) => owner
@ -67,7 +67,7 @@ object SmallTurretConverter {
private def MakeMountings(obj: WeaponTurret): List[InventoryItemData.InventoryItem] = {
obj.Weapons
.map({
case ((index, slot)) =>
case (index, slot) =>
val equip: Equipment = slot.Equipment.get
val equipDef = equip.Definition
InventoryItemData(equipDef.ObjectId, equip.GUID, index, equipDef.Packet.ConstructorData(equip).get)

View file

@ -19,10 +19,10 @@ class TRAPConverter extends ObjectCreateConverter[TrapDeployable]() {
obj.Faction,
bops = false,
alternate = false,
true,
v1 = true,
None,
jammered = false,
None,
false,
Some(true),
None,
obj.OwnerGuid match {
case Some(owner) => owner
@ -42,9 +42,9 @@ class TRAPConverter extends ObjectCreateConverter[TrapDeployable]() {
obj.Faction,
bops = false,
alternate = true,
true,
v1 = true,
None,
false,
jammered = false,
Some(true),
None,
PlanetSideGUID(0)

View file

@ -455,7 +455,7 @@ object GamePacketOpcode extends Enumeration {
case 0x7f => game.AvatarStatisticsMessage.decode
// OPCODES 0x80-8f
case 0x80 => noDecoder(GenericObjectAction2Message)
case 0x80 => game.GenericObjectAction2Message.decode
case 0x81 => game.DestroyDisplayMessage.decode
case 0x82 => noDecoder(TriggerBotAction)
case 0x83 => game.SquadWaypointRequest.decode

View file

@ -0,0 +1,33 @@
// Copyright (c) 2024 PSForever
package net.psforever.packet.game
import net.psforever.packet.GamePacketOpcode.Type
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import net.psforever.types.PlanetSideGUID
import scodec.bits.BitVector
import scodec.codecs._
import scodec.{Attempt, Codec}
/**
* na
* @param unk na
* @param guid1 na
* @param guid2 na
*/
final case class GenericObjectAction2Message(
unk: Int,
guid1: PlanetSideGUID,
guid2: PlanetSideGUID
) extends PlanetSideGamePacket {
type Packet = GenericObjectActionMessage
def opcode: Type = GamePacketOpcode.GenericObjectAction2Message
def encode: Attempt[BitVector] = GenericObjectAction2Message.encode(this)
}
object GenericObjectAction2Message extends Marshallable[GenericObjectAction2Message] {
implicit val codec: Codec[GenericObjectAction2Message] = (
("unk" | uint(bits = 3)) :: //dword_D32FC0
("guid1" | PlanetSideGUID.codec) ::
("guid2" | PlanetSideGUID.codec)
).as[GenericObjectAction2Message]
}

View file

@ -1,10 +1,12 @@
// Copyright (c) 2016 PSForever.net to present
package net.psforever.packet.game
import net.psforever.packet.GamePacketOpcode.Type
import net.psforever.packet.game.PlanetsideAttributeEnum.PlanetsideAttributeEnum
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import net.psforever.types.PlanetSideGUID
import scodec.Codec
import scodec.bits.BitVector
import scodec.{Attempt, Codec}
import scodec.codecs._
/**
@ -68,8 +70,8 @@ import scodec.codecs._
* `17 - BEP. Value seems to be the same as BattleExperienceMessage`<br>
* `18 - CEP.`<br>
* `19 - Anchors. Value is 0 to disengage, 1 to engage.`<br>
* `20 - Control console hacking, affects CC timer, yellow base warning lights and message "The FactionName has hacked into BaseName".
* Format is: Time left - 2 bytes, faction - 1 byte (1-4), isResecured - 1 byte (0-1)`<br>
* `20 - Control console hacking, affects CC timer, yellow base warning lights and message "The FactionName has hacked into BaseName".`
* Format is: Time left - 2 bytes, faction - 1 byte (1-4), isResecured - 1 byte (0-1)<br>
* <ul>
* <li>65535 segments per faction in deciseconds (seconds * 10)</li>
* <li>0-65535 = Neutral 0 seconds to 1h 49m 14s - 0x0000 to 0xFFFF</li>
@ -134,8 +136,8 @@ import scodec.codecs._
* `31 - Looking for Squad info (marquee and ui):`<br>
* <ul>
* <li>0 - LFS</li>
* <li>1 is LFSM (Looking for Squad Members)`</li>
* <li>`n` is the supplemental squad identifier number; same as "LFS;" for the leader, sets "LFSM" after the first manual flagging`</li>
* <li>1 is LFSM (Looking for Squad Members)</li>
* <li>`n` is the supplemental squad identifier number; same as "LFS;" for the leader, sets "LFSM" after the first manual flagging</li>
* </ul>
* `32 - Maintain the squad role index, when a member of a squad`<br>
* `35 - Battle Rank`<br>
@ -204,8 +206,8 @@ import scodec.codecs._
final case class PlanetsideAttributeMessage(guid: PlanetSideGUID, attribute_type: Int, attribute_value: Long)
extends PlanetSideGamePacket {
type Packet = PlanetsideAttributeMessage
def opcode = GamePacketOpcode.PlanetsideAttributeMessage
def encode = PlanetsideAttributeMessage.encode(this)
def opcode: Type = GamePacketOpcode.PlanetsideAttributeMessage
def encode: Attempt[BitVector] = PlanetsideAttributeMessage.encode(this)
}
object PlanetsideAttributeMessage extends Marshallable[PlanetsideAttributeMessage] {
@ -235,5 +237,6 @@ object PlanetsideAttributeMessage extends Marshallable[PlanetsideAttributeMessag
object PlanetsideAttributeEnum extends Enumeration {
type PlanetsideAttributeEnum = Value
val ControlConsoleHackUpdate = Value(20)
val ControlConsoleHackUpdate: PlanetsideAttributeEnum.Value = Value(20)
val OwnershipAssignment: PlanetsideAttributeEnum.Value = Value(21)
}

View file

@ -21,7 +21,10 @@ object AegisShieldGeneratorData extends Marshallable[AegisShieldGeneratorData] {
implicit val codec: Codec[AegisShieldGeneratorData] = (
("deploy" | CommonFieldDataWithPlacement.codec) ::
("health" | uint8L) ::
uint32 :: uint32 :: uint32 :: uint4L //100 bits
uint32 ::
uint32 ::
uint32 ::
uint4L
).exmap[AegisShieldGeneratorData](
{
case deploy :: health :: 0 :: 0 :: 0 :: 0 :: HNil =>

View file

@ -72,7 +72,7 @@ object BattleFrameRoboticsData extends Marshallable[BattleFrameRoboticsData] {
("proper_anim" | bool) :: //when unflagged, bfr stands, even if unmanned
("unk3" | uint4) ::
("show_bfr_shield" | bool) ::
optional(bool, target = "inventory" | MountableInventory.custom_inventory_codec(pos.vel.isDefined, VehicleFormat.Battleframe))
("inventory" | optional(bool, MountableInventory.custom_inventory_codec(pos.vel.isDefined, VehicleFormat.Battleframe)))
}
).exmap[BattleFrameRoboticsData] (
{
@ -107,7 +107,7 @@ object BattleFrameRoboticsData extends Marshallable[BattleFrameRoboticsData] {
("unk3" | uint4) ::
("show_bfr_shield" | bool) ::
("unk4" | bool) ::
optional(bool, target = "inventory" | MountableInventory.custom_inventory_codec(pos.vel.isDefined, VehicleFormat.BattleframeFlight))
("inventory" | optional(bool, MountableInventory.custom_inventory_codec(pos.vel.isDefined, VehicleFormat.BattleframeFlight)))
}
).exmap[BattleFrameRoboticsData] (
{

View file

@ -44,7 +44,7 @@ object CaptureFlagData extends Marshallable[CaptureFlagData] {
("targetBaseGuid" | uint8L) ::
uint8L ::
("milliseconds_remaining" | uint32L) ::
uint(1)
uint(bits = 1)
).exmap[CaptureFlagData](
{
case pos :: faction :: false :: 4 :: 0 :: owningBaseGuid :: 0 :: targetBaseGuid :: 0 :: milliseconds_remaining :: 0 :: HNil =>

View file

@ -406,7 +406,7 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
("unk6" | bool) :: //stream misalignment when set
("charging_pose" | bool) ::
("unk7" | bool) :: //alternate charging pose?
optional(bool, "on_zipline" | zipline_codec)
("on_zipline" | optional(bool, zipline_codec))
).exmap[CharacterAppearanceB](
{
case outfit_id :: outfit :: logo :: u1 :: bpack :: u2 :: u3 :: u4 :: facingPitch :: facingYawUpper :: lfs :: gstate :: cloaking :: u5 :: u6 :: charging :: u7 :: zipline :: HNil =>
@ -493,5 +493,5 @@ object CharacterAppearanceData extends Marshallable[CharacterAppearanceData] {
}
)
implicit val codec: Codec[CharacterAppearanceData] = codec(0)
implicit val codec: Codec[CharacterAppearanceData] = codec(name_padding = 0)
}

View file

@ -18,13 +18,13 @@ import shapeless.{::, HNil}
object ImplantEffects extends Enumeration {
type Type = Value
val SurgeEffects = Value(9)
val PersonalShieldEffects = Value(5)
val DarklightEffects = Value(3)
val RegenEffects = Value(0)
val NoEffects = Value(1)
val SurgeEffects: ImplantEffects.Value = Value(9)
val PersonalShieldEffects: ImplantEffects.Value = Value(5)
val DarklightEffects: ImplantEffects.Value = Value(3)
val RegenEffects: ImplantEffects.Value = Value(0)
val NoEffects: ImplantEffects.Value = Value(1)
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint4L)
implicit val codec: Codec[ImplantEffects.Value] = PacketHelpers.createEnumerationCodec(this, uint4L)
}
/**
@ -120,9 +120,9 @@ object CharacterData extends Marshallable[CharacterData] {
("armor" | uint8L) ::
(("uniform_upgrade" | UniformStyle.codec) >>:~ { style =>
uint(bits = 3) :: //uniform_upgrade is actually interpreted as a 6u field, but the lower 3u seems to be discarded
("command_rank" | uintL(3)) ::
listOfN(uint2, "implant_effects" | ImplantEffects.codec) ::
("cosmetics" | conditional(BattleRank.showCosmetics(style), Cosmetic.codec))
("command_rank" | uint(bits = 3)) ::
("implant_effects" | listOfN(uint2, ImplantEffects.codec)) ::
("cosmetics" | conditional(BattleRank.showCosmetics(style), Cosmetic.codec))
})
).exmap[CharacterData](
{
@ -158,24 +158,24 @@ object CharacterData extends Marshallable[CharacterData] {
def codec_seated(is_backpack: Boolean): Codec[CharacterData] =
(
("uniform_upgrade" | UniformStyle.codec) >>:~ { style =>
uint(3) :: //uniform_upgrade is actually interpreted as a 6u field, but the lower 3u seems to be discarded
("command_rank" | uintL(3)) ::
listOfN(uint2, "implant_effects" | ImplantEffects.codec) ::
("cosmetics" | conditional(BattleRank.showCosmetics(style), Cosmetic.codec))
uint(bits = 3) :: //uniform_upgrade is actually interpreted as a 6u field, but the lower 3u seems to be discarded
("command_rank" | uintL(bits = 3)) ::
("implant_effects" | listOfN(uint2, ImplantEffects.codec)) ::
("cosmetics" | conditional(BattleRank.showCosmetics(style), Cosmetic.codec))
}
).exmap[CharacterData](
{
case uniform :: unk :: cr :: implant_effects :: cosmetics :: HNil =>
Attempt.Successful(
new CharacterData(
100,
0,
health = 100,
armor = 0,
uniform,
unk,
cr,
implant_effects,
cosmetics
)(is_backpack, true)
)(is_backpack, is_seated = true)
)
case _ =>
@ -195,5 +195,5 @@ object CharacterData extends Marshallable[CharacterData] {
}
)
implicit val codec: Codec[CharacterData] = codec(false)
implicit val codec: Codec[CharacterData] = codec(is_backpack = false)
}

View file

@ -75,19 +75,19 @@ object CommonFieldData extends Marshallable[CommonFieldData] {
* @return a `CommonFieldData` object
*/
def apply(): CommonFieldData =
CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, false, None, false, None, None, PlanetSideGUID(0))
CommonFieldData(PlanetSideEmpire.NEUTRAL, bops = false, alternate = false, v1 = false, None, jammered = false, None, None, PlanetSideGUID(0))
def apply(faction: PlanetSideEmpire.Value): CommonFieldData =
CommonFieldData(faction, false, false, false, None, false, None, None, PlanetSideGUID(0))
CommonFieldData(faction, bops = false, alternate = false, v1 = false, None, jammered = false, None, None, PlanetSideGUID(0))
def apply(faction: PlanetSideEmpire.Value, unk: Int): CommonFieldData =
CommonFieldData(faction, false, false, unk > 1, None, unk > 0, None, None, PlanetSideGUID(0))
CommonFieldData(faction, bops = false, alternate = false, v1 = unk > 1, None, jammered = unk > 0, None, None, PlanetSideGUID(0))
def apply(faction: PlanetSideEmpire.Value, unk: Int, player_guid: PlanetSideGUID): CommonFieldData =
CommonFieldData(faction, false, false, unk > 1, None, unk > 0, None, None, player_guid)
CommonFieldData(faction, bops = false, alternate = false, v1 = unk > 1, None, jammered = unk > 0, None, None, player_guid)
def apply(faction: PlanetSideEmpire.Value, destroyed: Boolean, unk: Int): CommonFieldData =
CommonFieldData(faction, false, destroyed, unk > 1, None, unk > 0, None, None, PlanetSideGUID(0))
CommonFieldData(faction, bops = false, alternate = destroyed, v1 = unk > 1, None, jammered = unk > 0, None, None, PlanetSideGUID(0))
def apply(
faction: PlanetSideEmpire.Value,
@ -95,7 +95,7 @@ object CommonFieldData extends Marshallable[CommonFieldData] {
unk: Int,
player_guid: PlanetSideGUID
): CommonFieldData =
CommonFieldData(faction, false, destroyed, unk > 1, None, unk % 1 == 1, None, None, player_guid)
CommonFieldData(faction, bops = false, alternate = destroyed, v1 = unk > 1, None, jammered = false/*unk % 1 == 1*/, None, None, player_guid)
def apply(
faction: PlanetSideEmpire.Value,
@ -107,7 +107,7 @@ object CommonFieldData extends Marshallable[CommonFieldData] {
): CommonFieldData = {
val jammeredField = if (jammered) { Some(0) }
else { None }
CommonFieldData(faction, bops, destroyed, unk > 1, None, unk % 1 == 1, None, jammeredField, player_guid)
CommonFieldData(faction, bops, destroyed, unk > 1, None, jammered = false/*unk % 1 == 1*/, None, jammeredField, player_guid)
}
def codec(extra: Boolean): Codec[CommonFieldData] =
@ -116,9 +116,9 @@ object CommonFieldData extends Marshallable[CommonFieldData] {
("bops" | bool) ::
("alternate" | bool) ::
("v1" | bool) :: //the purpose of this bit changes depending on the previous bit
conditional(extra, "v2" | CommonFieldDataExtra.codec(unk1 = false)) ::
("v2" | conditional(extra, CommonFieldDataExtra.codec(unk1 = false))) ::
("jammered" | bool) ::
optional(bool, "v5" | uint16L) ::
("v5" | optional(bool, uint16L)) ::
("guid" | PlanetSideGUID.codec)
).xmap[CommonFieldData](
{
@ -139,9 +139,9 @@ object CommonFieldData extends Marshallable[CommonFieldData] {
("bops" | bool) ::
("alternate" | bool) ::
("v1" | bool) :: //though the code path differs depending on the previous bit, this one gets read one way or another
conditional(extra, codec = "v2" | CommonFieldDataExtra.codec(unk1 = false)) ::
("v2" | conditional(extra, CommonFieldDataExtra.codec(unk1 = false))) ::
("jammered" | bool) ::
optional(bool, "v5" | uint16L) ::
("v5" | optional(bool, uint16L)) ::
("v4" | bool) ::
("guid" | PlanetSideGUID.codec)
).exmap[CommonFieldData](
@ -158,5 +158,5 @@ object CommonFieldData extends Marshallable[CommonFieldData] {
}
)
val codec2: Codec[CommonFieldData] = codec2(false)
val codec2: Codec[CommonFieldData] = codec2(extra = false)
}

View file

@ -41,7 +41,7 @@ object DetailedAmmoBoxData extends Marshallable[DetailedAmmoBoxData] {
def apply(unk: Int, mag: Int): DetailedAmmoBoxData = {
DetailedAmmoBoxData(
CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, unk > 0, None, false, None, None, PlanetSideGUID(0)),
CommonFieldData(PlanetSideEmpire.NEUTRAL, bops = false, alternate = false, v1 = unk > 0, None, jammered = false, None, None, PlanetSideGUID(0)),
mag
)
}

View file

@ -23,7 +23,7 @@ final case class ImplantEntry(implant: ImplantType, initialization: Option[Int],
extends StreamBitSize {
override def bitsize: Long = {
val timerSize = initialization match {
case Some(_) => 8L;
case Some(_) => 8L
case None => 1L
}
9L + timerSize
@ -271,7 +271,7 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
private val implant_entry_codec: Codec[ImplantEntry] = (
("implant" | uint8L) ::
(bool >>:~ { guard =>
newcodecs.binary_choice(guard, uint(1), uint8L).hlist
newcodecs.binary_choice(guard, uint(bits = 1), uint8L).hlist
})
).xmap[ImplantEntry](
{
@ -305,7 +305,7 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
(
uint8 >>:~ { size =>
conditional(size > 0, dcd_extra1_codec(padFunc(size))) ::
PacketHelpers.listOfNSized(size - 1, dcd_extra1_codec(0))
PacketHelpers.listOfNSized(size - 1, dcd_extra1_codec(pad = 0))
}
).xmap[List[DCDExtra1]](
{
@ -376,7 +376,7 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
* `Codec` for a `ImprintingProgress` object.
*/
private val imprint_progress_codec: Codec[ImprintingProgress] = (
uint(5) ::
uint(bits = 5) ::
uint8L
).as[ImprintingProgress]
@ -464,7 +464,7 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
def paddingCalculations(contextOffset: Option[Int], implants: List[ImplantEntry], prevLists: List[List[Any]])(
currListLen: Long
): Int = {
paddingCalculations(3, contextOffset, implants, prevLists)(currListLen)
paddingCalculations(base = 3, contextOffset, implants, prevLists)(currListLen)
}
/**
@ -532,9 +532,9 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
("stamina" | uint16L) ::
conditional(suit == ExoSuitType.MAX, uint32L) ::
("unk6" | uint16L) ::
("unk7" | uint(3)) ::
("unk7" | uint(bits = 3)) ::
("unk8" | uint32L) ::
("unk9" | PacketHelpers.listOfNSized(6, uint16L)) :: //always length of 6
("unk9" | PacketHelpers.listOfNSized(size = 6, uint16L)) :: //always length of 6
("certs" | listOfN(uint8L, Certification.codec))
).exmap[DetailedCharacterA](
{
@ -591,7 +591,7 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
def b_codec(bep: Long, pad_length: Option[Int]): Codec[DetailedCharacterB] =
(
optional(bool, "unk1" | uint32L) ::
("unk1" | optional(bool, uint32L)) ::
(("implants" | PacketHelpers.listOfNSized(
BattleRank.withExperience(bep).implantSlots,
implant_entry_codec
@ -607,17 +607,17 @@ object DetailedCharacterData extends Marshallable[DetailedCharacterData] {
("unk6" | uint32L) ::
("unk7" | uint32L) ::
("unk8" | uint32L) ::
(optional(isFalse, "imprinting" | imprint_progress_codec) >>:~ { imprinting =>
(("imprinting" | optional(isFalse, imprint_progress_codec)) >>:~ { imprinting =>
("unkA" | listOfN(uint16L, uint32L)) ::
("unkB" | unkBCodec(
paddingCalculations(
displaceByOptionTest(pad_length, imprinting, 5),
displaceByOptionTest(pad_length, imprinting, value = 5),
implants,
List(imprinting.toList, tut, fte, unk3, unk2)
)
)) ::
("unkC" | bool) ::
conditional(bep >= BattleRank.BR24.experience, "cosmetics" | Cosmetic.codec)
("cosmetics" | conditional(bep >= BattleRank.BR24.experience, Cosmetic.codec))
})
}
}

View file

@ -27,7 +27,7 @@ object DetailedConstructionToolData extends Marshallable[DetailedConstructionToo
def apply(data: CommonFieldData): DetailedConstructionToolData = DetailedConstructionToolData(data, 0)
implicit val codec: Codec[DetailedConstructionToolData] = (
("data" | CommonFieldData.codec(false)) ::
("data" | CommonFieldData.codec(extra = false)) ::
uint8 :: //n > 1 produces a stack of construction items (tends to crash the client)
("mode" | uint16) ::
uint2 ::

View file

@ -72,7 +72,7 @@ object DetailedPlayerData extends Marshallable[DetailedPlayerData] {
drawn_slot: DrawnSlot.Value
): DetailedPlayerData = {
val appearance = basic_appearance(5)
DetailedPlayerData(None, appearance, character_data(appearance.altModelBit), Some(inventory), drawn_slot)(false)
DetailedPlayerData(None, appearance, character_data(appearance.altModelBit), Some(inventory), drawn_slot)(position_defined = false)
}
/**
@ -91,7 +91,7 @@ object DetailedPlayerData extends Marshallable[DetailedPlayerData] {
drawn_slot: DrawnSlot.Value
): DetailedPlayerData = {
val appearance = basic_appearance(5)
DetailedPlayerData(None, appearance, character_data(appearance.altModelBit), None, drawn_slot)(false)
DetailedPlayerData(None, appearance, character_data(appearance.altModelBit), None, drawn_slot)(position_defined = false)
}
/**
@ -113,7 +113,7 @@ object DetailedPlayerData extends Marshallable[DetailedPlayerData] {
drawn_slot: DrawnSlot.Value
): DetailedPlayerData = {
val appearance = basic_appearance(PlayerData.PaddingOffset(Some(pos)))
DetailedPlayerData(Some(pos), appearance, character_data(appearance.altModelBit), Some(inventory), drawn_slot)(true)
DetailedPlayerData(Some(pos), appearance, character_data(appearance.altModelBit), Some(inventory), drawn_slot)(position_defined = true)
}
/**
@ -133,14 +133,14 @@ object DetailedPlayerData extends Marshallable[DetailedPlayerData] {
drawn_slot: DrawnSlot.Value
): DetailedPlayerData = {
val appearance = basic_appearance(PlayerData.PaddingOffset(Some(pos)))
DetailedPlayerData(Some(pos), appearance, character_data(appearance.altModelBit), None, drawn_slot)(true)
DetailedPlayerData(Some(pos), appearance, character_data(appearance.altModelBit), None, drawn_slot)(position_defined = true)
}
def codec(position_defined: Boolean): Codec[DetailedPlayerData] =
(conditional(position_defined, "pos" | PlacementData.codec) >>:~ { pos =>
("basic_appearance" | CharacterAppearanceData.codec(PlayerData.PaddingOffset(pos))) >>:~ { app =>
("character_data" | DetailedCharacterData.codec(app.a.exosuit, app.altModelBit)) ::
optional(bool, "inventory" | InventoryData.codec_detailed) ::
("inventory" | optional(bool, InventoryData.codec_detailed)) ::
("drawn_slot" | DrawnSlot.codec) ::
bool //usually false
}
@ -155,5 +155,5 @@ object DetailedPlayerData extends Marshallable[DetailedPlayerData] {
}
)
implicit val codec: Codec[DetailedPlayerData] = codec(false)
implicit val codec: Codec[DetailedPlayerData] = codec(position_defined = false)
}

View file

@ -28,7 +28,7 @@ object DetailedREKData extends Marshallable[DetailedREKData] {
uint16L ::
uint4L ::
("unk" | uint8) ::
uint(7)
uint(bits = 7)
).exmap[DetailedREKData](
{
case data :: 2 :: 0 :: 8 :: unk :: 0 :: HNil =>

View file

@ -56,11 +56,11 @@ object DetailedWeaponData extends Marshallable[DetailedWeaponData] {
DetailedWeaponData(
CommonFieldData(
PlanetSideEmpire(unk1 & 3),
false,
false,
(unk2 & 8) == 8,
bops = false,
alternate = false,
v1 = (unk2 & 8) == 8,
None,
(unk2 & 4) == 4,
jammered = (unk2 & 4) == 4,
None,
None,
PlanetSideGUID(0)
@ -92,11 +92,11 @@ object DetailedWeaponData extends Marshallable[DetailedWeaponData] {
DetailedWeaponData(
CommonFieldData(
PlanetSideEmpire(unk1 & 3),
false,
false,
(unk2 & 8) == 8,
bops = false,
alternate = false,
v1 = (unk2 & 8) == 8,
None,
(unk2 & 4) == 4,
jammered = (unk2 & 4) == 4,
None,
None,
PlanetSideGUID(0)
@ -112,7 +112,7 @@ object DetailedWeaponData extends Marshallable[DetailedWeaponData] {
uint8 ::
("fire_mode" | uint8) ::
uint2 ::
optional(bool, "ammo" | InventoryData.codec_detailed) ::
("ammo" | optional(bool, InventoryData.codec_detailed)) ::
("unk" | bool)
).exmap[DetailedWeaponData](
{

View file

@ -46,9 +46,9 @@ object DroppodData extends Marshallable[DroppodData] {
("basic" | CommonFieldDataWithPlacement.codec) ::
bool ::
("health" | uint8L) :: //health
uintL(5) :: //0x0
uintL(bits = 5) :: //0x0
uint4L :: //0xF
uintL(6) :: //0x0
uintL(bits = 6) :: //0x0
("boosters" | uint4L) :: //0x9 on standby, 0x0 when burning and occupied (basic.player_guid?)
("unk" | bool)
).exmap[DroppodData](

View file

@ -32,7 +32,7 @@ object HandheldData extends Marshallable[HandheldData] {
implicit val codec: Codec[HandheldData] = (
("data" | CommonFieldData.codec) ::
("mode" | uint8) ::
("unk" | uint(3))
("unk" | uint(bits = 3))
).exmap[HandheldData](
{
case data :: mode :: unk :: HNil =>

View file

@ -37,7 +37,7 @@ object InternalSlot {
* Used for `0x18` `ObjectCreateDetailedMessage` packets
*/
val codec_detailed: Codec[InternalSlot] = (
("objectClass" | uintL(11)) >>:~ { obj_cls =>
("objectClass" | uintL(bits = 11)) >>:~ { obj_cls =>
("guid" | PlanetSideGUID.codec) ::
("parentSlot" | PacketHelpers.encodedStringSize) ::
("obj" | ObjectClass.selectDataDetailedCodec(obj_cls)) //it's fine for this call to fail
@ -57,7 +57,7 @@ object InternalSlot {
* Used for `0x17` `ObjectCreateMessage` packets
*/
val codec: Codec[InternalSlot] = (
("objectClass" | uintL(11)) >>:~ { obj_cls =>
("objectClass" | uintL(bits = 11)) >>:~ { obj_cls =>
("guid" | PlanetSideGUID.codec) ::
("parentSlot" | PacketHelpers.encodedStringSize) ::
("obj" | ObjectClass.selectDataCodec(obj_cls)) //it's fine for this call to fail

View file

@ -30,10 +30,10 @@ object LargeDeployableData extends Marshallable[LargeDeployableData] {
implicit val codec: Codec[LargeDeployableData] = (
("deploy" | CommonFieldDataWithPlacement.codec2) ::
("health" | uint8L) ::
uintL(7) ::
uintL(bits = 7) ::
uint4L ::
uint2L ::
optional(bool, "internals" | InventoryData.codec)
("internals" | optional(bool, InventoryData.codec))
).exmap[LargeDeployableData](
{
case deploy :: health :: 0 :: 0xf :: 0 :: internals :: HNil =>

View file

@ -31,9 +31,11 @@ object LockerContainerData extends Marshallable[LockerContainerData] {
new LockerContainerData(Some(InventoryData(inventory)))
implicit val codec: Codec[LockerContainerData] = (
uint32 :: uint32 :: uint(17) ::
uint32 ::
uint32 ::
uint(bits = 17) ::
uint2L ::
uint(21) ::
uint(bits = 21) ::
("inventory" | optional(bool, InventoryData.codec))
).exmap[LockerContainerData](
{

View file

@ -1216,22 +1216,21 @@ object ObjectClass {
case ObjectClass.advanced_ace => DroppedItemData(HandheldData.codec, "advanced ace")
case ObjectClass.router_telepad => DroppedItemData(HandheldData.codec, "router telepad")
case ObjectClass.boomer_trigger => DroppedItemData(HandheldData.codec, "boomer trigger")
case ObjectClass.boomer => ConstructorData(CommonFieldDataWithPlacement.codec2, "ace deployable")
case ObjectClass.he_mine => ConstructorData(CommonFieldDataWithPlacement.codec2, "ace deployable")
case ObjectClass.jammer_mine => ConstructorData(CommonFieldDataWithPlacement.codec2, "ace deployable")
case ObjectClass.motionalarmsensor => ConstructorData(CommonFieldDataWithPlacement.codec2, "ace deployable")
case ObjectClass.sensor_shield => ConstructorData(CommonFieldDataWithPlacement.codec2, "ace deployable")
case ObjectClass.boomer => ConstructorData(CommonFieldDataWithPlacement.codec, "ace deployable")
case ObjectClass.he_mine => ConstructorData(CommonFieldDataWithPlacement.codec, "ace deployable")
case ObjectClass.jammer_mine => ConstructorData(CommonFieldDataWithPlacement.codec, "ace deployable")
case ObjectClass.motionalarmsensor => ConstructorData(CommonFieldDataWithPlacement.codec, "ace deployable")
case ObjectClass.sensor_shield => ConstructorData(CommonFieldDataWithPlacement.codec, "ace deployable")
case ObjectClass.spitfire_aa => ConstructorData(SmallTurretData.codec, "small turret")
case ObjectClass.spitfire_cloaked => ConstructorData(SmallTurretData.codec, "small turret")
case ObjectClass.spitfire_turret => ConstructorData(SmallTurretData.codec, "small turret")
case ObjectClass.tank_traps => ConstructorData(TRAPData.codec, "trap")
case ObjectClass.deployable_shield_generator =>
ConstructorData(AegisShieldGeneratorData.codec, "shield generator")
case ObjectClass.portable_manned_turret => ConstructorData(OneMannedFieldTurretData.codec, "field turret")
case ObjectClass.portable_manned_turret_nc => ConstructorData(OneMannedFieldTurretData.codec, "field turret")
case ObjectClass.portable_manned_turret_tr => ConstructorData(OneMannedFieldTurretData.codec, "field turret")
case ObjectClass.portable_manned_turret_vs => ConstructorData(OneMannedFieldTurretData.codec, "field turret")
case ObjectClass.router_telepad_deployable => DroppedItemData(TelepadDeployableData.codec, "telepad deployable")
case ObjectClass.deployable_shield_generator => ConstructorData(AegisShieldGeneratorData.codec, "shield generator")
case ObjectClass.portable_manned_turret => ConstructorData(OneMannedFieldTurretData.codec, "field turret")
case ObjectClass.portable_manned_turret_nc => ConstructorData(OneMannedFieldTurretData.codec, "field turret")
case ObjectClass.portable_manned_turret_tr => ConstructorData(OneMannedFieldTurretData.codec, "field turret")
case ObjectClass.portable_manned_turret_vs => ConstructorData(OneMannedFieldTurretData.codec, "field turret")
case ObjectClass.router_telepad_deployable => DroppedItemData(TelepadDeployableData.codec, "telepad deployable")
//projectiles
case ObjectClass.aphelion_plasma_cloud => ConstructorData(RadiationCloudData.codec, "radiation cloud")
case ObjectClass.aphelion_starfire_projectile => ConstructorData(RemoteProjectileData.codec, "projectile")

View file

@ -39,7 +39,6 @@ final case class OneMannedFieldTurretData(
}
object OneMannedFieldTurretData extends Marshallable[OneMannedFieldTurretData] {
/**
* Overloaded constructor that mandates information about the internal weapon of the field turret.
* @param deploy data common to objects spawned by the (advanced) adaptive construction engine
@ -55,10 +54,10 @@ object OneMannedFieldTurretData extends Marshallable[OneMannedFieldTurretData] {
PlanetSideGUID.codec :: //hoist/extract with the deploy.owner_guid in field above
bool ::
("health" | uint8L) ::
uint(5) ::
uint(bits = 5) ::
uint4 ::
uint2 ::
optional(bool, "internals" | InventoryData.codec)
("internals" | optional(bool, InventoryData.codec))
).exmap[OneMannedFieldTurretData](
{
case deploy :: player :: false :: health :: 0 :: 0xf :: 0 :: internals :: HNil =>

View file

@ -58,9 +58,9 @@ object OrbitalShuttleData extends Marshallable[OrbitalShuttleData] {
implicit val codec: Codec[OrbitalShuttleData] = (
("faction" | PlanetSideEmpire.codec) ::
uintL(25) ::
uintL(bits = 25) ::
uint8L :: //255
uintL(5) ::
uintL(bits = 5) ::
uint4L :: //7
uint2L
).exmap[OrbitalShuttleData](

View file

@ -76,7 +76,7 @@ object PlayerData extends Marshallable[PlayerData] {
): PlayerData = {
val appearance = basic_appearance(5)
PlayerData(None, appearance, character_data(appearance.altModelBit.isDefined, true), Some(inventory), drawn_slot)(
false
position_defined = false
)
}
@ -96,7 +96,7 @@ object PlayerData extends Marshallable[PlayerData] {
drawn_slot: DrawnSlot.Type
): PlayerData = {
val appearance = basic_appearance(5)
PlayerData(None, appearance, character_data(appearance.altModelBit.isDefined, true), None, drawn_slot)(false)
PlayerData(None, appearance, character_data(appearance.altModelBit.isDefined, true), None, drawn_slot)(position_defined = false)
}
/**
@ -124,7 +124,7 @@ object PlayerData extends Marshallable[PlayerData] {
character_data(appearance.altModelBit.isDefined, false),
Some(inventory),
drawn_slot
)(true)
)(position_defined = true)
}
/**
@ -144,7 +144,7 @@ object PlayerData extends Marshallable[PlayerData] {
drawn_slot: DrawnSlot.Type
): PlayerData = {
val appearance = basic_appearance(PaddingOffset(Some(pos)))
PlayerData(Some(pos), appearance, character_data(appearance.altModelBit.isDefined, false), None, drawn_slot)(true)
PlayerData(Some(pos), appearance, character_data(appearance.altModelBit.isDefined, false), None, drawn_slot)(position_defined = true)
}
/**
@ -205,7 +205,7 @@ object PlayerData extends Marshallable[PlayerData] {
CharacterData.codec(app.b.backpack),
CharacterData.codec_seated(app.b.backpack)
)) ::
optional(bool, "inventory" | InventoryData.codec) ::
("inventory" | optional(bool, InventoryData.codec)) ::
("drawn_slot" | DrawnSlot.codec) ::
bool //usually false
}
@ -232,14 +232,14 @@ object PlayerData extends Marshallable[PlayerData] {
(
("basic_appearance" | CharacterAppearanceData.codec(offset)) >>:~ { app =>
("character_data" | CharacterData.codec_seated(app.b.backpack)) ::
optional(bool, "inventory" | InventoryData.codec) ::
("inventory" | optional(bool, InventoryData.codec)) ::
("drawn_slot" | DrawnSlot.codec) ::
bool //usually false
}
).xmap[PlayerData](
{
case app :: data :: inv :: hand :: _ :: HNil =>
PlayerData(None, app, data, inv, hand)(false)
PlayerData(None, app, data, inv, hand)(position_defined = false)
},
{
case PlayerData(_, app, data, inv, hand) =>
@ -247,5 +247,5 @@ object PlayerData extends Marshallable[PlayerData] {
}
)
implicit val codec: Codec[PlayerData] = codec(false)
implicit val codec: Codec[PlayerData] = codec(position_defined = false)
}

View file

@ -23,7 +23,7 @@ object REKData extends Marshallable[REKData] {
implicit val codec: Codec[REKData] = (
("data" | CommonFieldData.codec2) ::
("unk1" | uint16) ::
("unk2" | uint(10))
("unk2" | uint(bits = 10))
).exmap[REKData](
{
case data :: u1 :: u2 :: HNil =>

View file

@ -26,19 +26,19 @@ object FlightPhysics extends Enumeration {
type Type = Value
//valid (extremely small distance) (requires non-zero unk4, unk5)
val State3 = Value(3)
val State3: FlightPhysics.Value = Value(3)
//valid (infinite) (if unk4 == 0 unk5 == 0, minimum distance + time)
val State4 = Value(4)
val State4: FlightPhysics.Value = Value(4)
//valid(infinite)
val State5 = Value(5)
val State5: FlightPhysics.Value = Value(5)
//valid (uses velocity) (infinite)
val State6 = Value(6)
val State6: FlightPhysics.Value = Value(6)
//valid (uses velocity) (infinite)
val State7 = Value(7)
val State7: FlightPhysics.Value = Value(7)
//valid (uses velocity) (time > 0 is infinite) (unk5 == 2)
val State15 = Value(15)
val State15: FlightPhysics.Value = Value(15)
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint4L)
implicit val codec: Codec[FlightPhysics.Value] = PacketHelpers.createEnumerationCodec(this, uint4L)
}
/**

View file

@ -32,7 +32,7 @@ final case class SmallTurretData(
case None =>
0
}
22L + deploySize + internalSize //8u + 7u + 4u + 2u + 1u
23L + deploySize + internalSize //1u + 8u + 7u + 4u + 2u + 1u
}
}
@ -49,15 +49,16 @@ object SmallTurretData extends Marshallable[SmallTurretData] {
new SmallTurretData(deploy, health, Some(internals))
implicit val codec: Codec[SmallTurretData] = (
("deploy" | CommonFieldDataWithPlacement.codec2) ::
("deploy" | CommonFieldDataWithPlacement.codec) ::
ignore(size = 1) ::
("health" | uint8L) ::
uintL(7) ::
uintL(bits = 7) ::
uint4L ::
uint2L ::
optional(bool, "internals" | InventoryData.codec)
("internals" | optional(bool, InventoryData.codec))
).exmap[SmallTurretData](
{
case deploy :: health :: 0 :: 0xf :: 0 :: internals :: HNil =>
case deploy :: _ :: health :: 0 :: 0xf :: 0 :: internals :: HNil =>
val (newHealth, newInternals) = if (health == 0 || internals.isEmpty || internals.get.contents.isEmpty) {
(0, None)
} else {
@ -75,7 +76,7 @@ object SmallTurretData extends Marshallable[SmallTurretData] {
} else {
(health, internals)
}
Attempt.successful(deploy :: newHealth :: 0 :: 0xf :: 0 :: newInternals :: HNil)
Attempt.successful(deploy :: () :: newHealth :: 0 :: 0xf :: 0 :: newInternals :: HNil)
}
)
}

View file

@ -14,20 +14,21 @@ import shapeless.{::, HNil}
*/
final case class TRAPData(deploy: CommonFieldDataWithPlacement, health: Int) extends ConstructorData {
override def bitsize: Long = {
22L + deploy.bitsize //8u + 7u + 4u + 3u
23L + deploy.bitsize //1u + 8u + 7u + 4u + 3u
}
}
object TRAPData extends Marshallable[TRAPData] {
implicit val codec: Codec[TRAPData] = (
("deploy" | CommonFieldDataWithPlacement.codec2) ::
("deploy" | CommonFieldDataWithPlacement.codec) ::
ignore(size = 1) ::
("health" | uint8L) ::
uint(7) ::
uint(bits = 7) ::
uint4L ::
uint(3)
uint(bits = 3)
).exmap[TRAPData](
{
case deploy :: health :: 0 :: 15 :: 0 :: HNil =>
case deploy :: _:: health :: 0 :: 15 :: 0 :: HNil =>
Attempt.successful(TRAPData(deploy, health))
case data =>
@ -35,7 +36,7 @@ object TRAPData extends Marshallable[TRAPData] {
},
{
case TRAPData(deploy, health) =>
Attempt.successful(deploy :: health :: 0 :: 15 :: 0 :: HNil)
Attempt.successful(deploy :: () :: health :: 0 :: 15 :: 0 :: HNil)
}
)
}

View file

@ -106,7 +106,7 @@ object VehicleData extends Marshallable[VehicleData] {
cloak: Boolean,
inventory: Option[InventoryData]
): VehicleData = {
VehicleData(pos, basic, false, health, false, false, driveState, false, false, cloak, None, inventory)(
VehicleData(pos, basic, unk3 = false, health, unk4 = false, no_mount_points = false, driveState, unk5 = false, unk6 = false, cloak = cloak, None, inventory)(
VehicleFormat.Normal
)
}
@ -128,7 +128,7 @@ object VehicleData extends Marshallable[VehicleData] {
format: UtilityVehicleData,
inventory: Option[InventoryData]
): VehicleData = {
VehicleData(pos, basic, false, health, false, false, driveState, false, false, cloak, Some(format), inventory)(
VehicleData(pos, basic, unk3 = false, health, unk4 = false, no_mount_points = false, driveState, unk5 = false, unk6 = false, cloak = cloak, Some(format), inventory)(
VehicleFormat.Utility
)
}
@ -150,7 +150,7 @@ object VehicleData extends Marshallable[VehicleData] {
format: VariantVehicleData,
inventory: Option[InventoryData]
): VehicleData = {
VehicleData(pos, basic, false, health, false, false, driveState, false, false, cloak, Some(format), inventory)(
VehicleData(pos, basic, unk3 = false, health, unk4 = false, no_mount_points = false, driveState, unk5 = false, unk6 = false, cloak = cloak, Some(format), inventory)(
VehicleFormat.Variant
)
}
@ -227,8 +227,8 @@ object VehicleData extends Marshallable[VehicleData] {
("unk5" | bool) :: //unknown but generally false; can cause stream misalignment if set when unexpectedly
("unk6" | bool) ::
("cloak" | bool) :: //cloak as wraith, phantasm
conditional(vehicle_type != VehicleFormat.Normal,"vehicle_format_data" | selectFormatReader(vehicle_type)) ::
optional(bool, target = "inventory" | MountableInventory.custom_inventory_codec(pos.vel.isDefined, vehicle_type))
("vehicle_format_data" | conditional(vehicle_type != VehicleFormat.Normal, selectFormatReader(vehicle_type))) ::
("inventory" | optional(bool, MountableInventory.custom_inventory_codec(pos.vel.isDefined, vehicle_type)))
}
).exmap[VehicleData](
{

View file

@ -54,8 +54,8 @@ object WeaponData extends Marshallable[WeaponData] {
WeaponData(
CommonFieldData(
PlanetSideEmpire(unk1 & 3),
false,
false,
bops = false,
alternate = false,
(unk2 & 8) == 8,
None,
(unk2 & 4) == 4,
@ -91,8 +91,8 @@ object WeaponData extends Marshallable[WeaponData] {
WeaponData(
CommonFieldData(
PlanetSideEmpire(unk1 & 3),
false,
false,
bops = false,
alternate = false,
(unk2 & 8) == 8,
None,
(unk2 & 4) == 4,
@ -137,8 +137,8 @@ object WeaponData extends Marshallable[WeaponData] {
WeaponData(
CommonFieldData(
PlanetSideEmpire(unk1 & 3),
false,
false,
bops = false,
alternate = false,
(unk2 & 8) == 8,
None,
(unk2 & 4) == 4,

View file

@ -87,17 +87,6 @@ class AvatarService(zone: Zone) extends Actor {
AvatarResponse.EnvironmentalDamage(player_guid, source_guid, amount)
)
)
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))

View file

@ -4,7 +4,6 @@ package net.psforever.services.avatar
import net.psforever.objects.Player
import net.psforever.objects.avatar.scoring.KDAStat
import net.psforever.objects.ballistics.Projectile
import net.psforever.objects.ce.Deployable
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.inventory.InventoryItem
import net.psforever.objects.serverobject.environment.interaction.common.Watery.OxygenStateTarget
@ -44,7 +43,6 @@ object AvatarAction {
final case class ConcealPlayer(player_guid: PlanetSideGUID) extends Action
final case class EnvironmentalDamage(player_guid: PlanetSideGUID, source_guid: PlanetSideGUID, amount: Int)
extends Action
final case class DeployItem(player_guid: PlanetSideGUID, item: Deployable) extends Action
final case class DeactivateImplantSlot(player_guid: PlanetSideGUID, slot: Int) extends Action
final case class ActivateImplantSlot(player_guid: PlanetSideGUID, slot: Int) extends Action
final case class Destroy(victim: PlanetSideGUID, killer: PlanetSideGUID, weapon: PlanetSideGUID, pos: Vector3)

View file

@ -4,7 +4,7 @@ package net.psforever.services.local
import akka.actor.{Actor, Props}
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{TriggeredEffect, TriggeredEffectLocation}
import net.psforever.packet.game.{ObjectCreateMessage, TriggeredEffect, TriggeredEffectLocation}
import net.psforever.services.local.support.CaptureFlagManager
import net.psforever.types.PlanetSideGUID
import net.psforever.services.local.support._
@ -45,6 +45,16 @@ class LocalService(zone: Zone) extends Actor {
case LocalServiceMessage(forChannel, action) =>
action match {
case LocalAction.DeployItem(item) =>
val definition = item.Definition
val objectData = definition.Packet.ConstructorData(item).get
LocalEvents.publish(
LocalServiceResponse(
s"/$forChannel/Local",
Service.defaultPlayerGUID,
LocalResponse.SendResponse(ObjectCreateMessage(definition.ObjectId, item.GUID, objectData))
)
)
case LocalAction.DeployableMapIcon(player_guid, behavior, deployInfo) =>
LocalEvents.publish(
LocalServiceResponse(

View file

@ -27,6 +27,7 @@ object LocalServiceMessage {
object LocalAction {
trait Action
final case class DeployItem(item: Deployable) extends Action
final case class DeployableMapIcon(
player_guid: PlanetSideGUID,
behavior: DeploymentAction.Value,

View file

@ -0,0 +1,30 @@
// Copyright (c) 2017 PSForever
package game
import org.specs2.mutable._
import net.psforever.packet._
import net.psforever.packet.game._
import net.psforever.types.PlanetSideGUID
import scodec.bits._
class GenericObjectAction2MessageTest extends Specification {
val string = hex"80 38C139212 0"
"decode" in {
PacketCoding.decodePacket(string).require match {
case GenericObjectAction2Message(unk, guid1, guid2) =>
unk mustEqual 1
guid1 mustEqual PlanetSideGUID(2502)
guid2 mustEqual PlanetSideGUID(2505)
case _ =>
ko
}
}
"encode" in {
val msg = GenericObjectAction2Message(1, PlanetSideGUID(2502), PlanetSideGUID(2505))
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
pkt mustEqual string
}
}

View file

@ -32,9 +32,9 @@ class SmallTurretDataTest extends Specification {
deploy.v1 mustEqual true
deploy.v2.isEmpty mustEqual true
deploy.jammered mustEqual false
deploy.v4.contains(false) mustEqual true
deploy.v4.isEmpty mustEqual true
deploy.v5.isEmpty mustEqual true
deploy.guid mustEqual PlanetSideGUID(7742)
deploy.guid mustEqual PlanetSideGUID(3871)
health mustEqual 0
case _ =>
@ -63,9 +63,9 @@ class SmallTurretDataTest extends Specification {
deploy.v1 mustEqual true
deploy.v2.isEmpty mustEqual true
deploy.jammered mustEqual false
deploy.v4.contains(true) mustEqual true
deploy.v4.isEmpty mustEqual true
deploy.v5.isEmpty mustEqual true
deploy.guid mustEqual PlanetSideGUID(8208)
deploy.guid mustEqual PlanetSideGUID(4232)
health mustEqual 255
@ -124,7 +124,7 @@ class SmallTurretDataTest extends Specification {
val obj = SmallTurretData(
CommonFieldDataWithPlacement(
PlacementData(Vector3(4577.7812f, 5624.828f, 72.046875f), Vector3(0, 2.8125f, 264.375f)),
CommonFieldData(PlanetSideEmpire.NC, false, true, true, None, false, Some(false), None, PlanetSideGUID(7742))
CommonFieldData(PlanetSideEmpire.NC, bops = false, alternate = true, v1 = true, None, jammered = false, None, None, PlanetSideGUID(3871))
),
0
)
@ -137,7 +137,7 @@ class SmallTurretDataTest extends Specification {
val obj = SmallTurretData(
CommonFieldDataWithPlacement(
PlacementData(Vector3(4527.633f, 6271.3594f, 70.265625f), Vector3(0, 0, 154.6875f)),
CommonFieldData(PlanetSideEmpire.VS, false, false, true, None, false, Some(true), None, PlanetSideGUID(8208))
CommonFieldData(PlanetSideEmpire.VS, bops = false, alternate = false, v1 = true, None, jammered = false, None, None, PlanetSideGUID(4232))
),
255,
InventoryData(
@ -149,11 +149,11 @@ class SmallTurretDataTest extends Specification {
WeaponData(
CommonFieldData(
PlanetSideEmpire.NEUTRAL,
false,
false,
true,
bops = false,
alternate = false,
v1 = true,
None,
false,
jammered = false,
None,
None,
PlanetSideGUID(0)
@ -166,11 +166,11 @@ class SmallTurretDataTest extends Specification {
0,
CommonFieldData(
PlanetSideEmpire.NEUTRAL,
false,
false,
true,
bops = false,
alternate = false,
v1 = true,
None,
false,
jammered = false,
Some(false),
None,
PlanetSideGUID(0)

View file

@ -29,9 +29,9 @@ class TRAPDataTest extends Specification {
deploy.v1 mustEqual true
deploy.v2.isEmpty mustEqual true
deploy.jammered mustEqual false
deploy.v4.contains(true) mustEqual true
deploy.v4.isEmpty mustEqual true
deploy.v5.isEmpty mustEqual true
deploy.guid mustEqual PlanetSideGUID(4748)
deploy.guid mustEqual PlanetSideGUID(2502)
health mustEqual 255
case _ =>
ko
@ -45,7 +45,7 @@ class TRAPDataTest extends Specification {
val obj = TRAPData(
CommonFieldDataWithPlacement(
PlacementData(Vector3(3572.4453f, 3277.9766f, 114.0f), Vector3.z(90)),
CommonFieldData(PlanetSideEmpire.VS, false, false, true, None, false, Some(true), None, PlanetSideGUID(4748))
CommonFieldData(PlanetSideEmpire.VS, bops = false, alternate = false, v1 = true, None, jammered = false, None, None, PlanetSideGUID(2502))
),
255
)

View file

@ -57,7 +57,7 @@ class DeployableBehaviorSetupTest extends ActorTest {
case _ => assert(false, "self-setup test - no spawn fx")
}
eventsMsgs(1) match {
case AvatarServiceMessage("test", AvatarAction.DeployItem(PlanetSideGUID(0), obj)) =>
case LocalServiceMessage("test", LocalAction.DeployItem(obj)) =>
assert(obj eq jmine, "self-setup test - not same mine")
case _ =>
assert( false, "self-setup test - wrong deploy message")
@ -189,7 +189,7 @@ class DeployableBehaviorSetupOwnedP2Test extends FreedContextActorTest {
case _ => assert(false, "owned setup test, 2 - no spawn fx")
}
eventsMsgs(3) match {
case AvatarServiceMessage("test", AvatarAction.DeployItem(PlanetSideGUID(0), obj)) =>
case LocalServiceMessage("test", LocalAction.DeployItem(obj)) =>
assert(obj eq jmine, "owned setup test, 2 - not same mine")
case _ =>
assert( false, "owned setup test, 2 - wrong deploy message")

View file

@ -48,7 +48,7 @@ class TelepadDeployableNoRouterTest extends ActorTest {
val eventsMsgs = eventsProbe.receiveN(4, 10.seconds)
eventsMsgs.head match {
case AvatarServiceMessage("test", AvatarAction.DeployItem(PlanetSideGUID(0), obj)) =>
case LocalServiceMessage("test", LocalAction.DeployItem(obj)) =>
assert(obj eq telepad, "no-router telepad deployable testt - not same telepad")
case _ =>
assert( false, "no-router telepad deployable test - wrong deploy message")
@ -117,7 +117,7 @@ class TelepadDeployableNoActivationTest extends ActorTest {
val eventsMsgs = eventsProbe.receiveN(4, 10.seconds)
eventsMsgs.head match {
case AvatarServiceMessage("test", AvatarAction.DeployItem(PlanetSideGUID(0), obj)) =>
case LocalServiceMessage("test", LocalAction.DeployItem(obj)) =>
assert(obj eq telepad, "no-activate telepad deployable testt - not same telepad")
case _ =>
assert( false, "no-activate telepad deployable test - wrong deploy message")
@ -190,7 +190,7 @@ class TelepadDeployableAttemptTest extends ActorTest {
val eventsMsgs = eventsProbe.receiveN(2, 10.seconds)
val routerMsgs = routerProbe.receiveN(1, 10.seconds)
eventsMsgs.head match {
case AvatarServiceMessage("test", AvatarAction.DeployItem(PlanetSideGUID(0), obj)) =>
case LocalServiceMessage("test", LocalAction.DeployItem(obj)) =>
assert(obj eq telepad, "link attempt telepad deployable testt - not same telepad")
case _ =>
assert( false, "link attempt telepad deployable test - wrong deploy message")
@ -263,7 +263,7 @@ class TelepadDeployableResponseFromRouterTest extends FreedContextActorTest {
val eventsMsgs = eventsProbe.receiveN(9, 10.seconds)
eventsMsgs.head match {
case AvatarServiceMessage("test", AvatarAction.DeployItem(PlanetSideGUID(0), obj)) =>
case LocalServiceMessage("test", LocalAction.DeployItem(obj)) =>
assert(obj eq telepad, "link to router test - not same telepad")
case _ =>
assert( false, "link to router test - wrong deploy message")

View file

@ -78,6 +78,31 @@ class LocalService5Test extends ActorTest {
}
}
class DeployItemTest extends ActorTest {
ServiceManager.boot(system)
val service = system.actorOf(Props(classOf[LocalService], Zone.Nowhere), "deploy-item-test-service")
val objDef = GlobalDefinitions.motionalarmsensor
val obj = new SensorDeployable(objDef)
obj.Position = Vector3(1, 2, 3)
obj.Orientation = Vector3(4, 5, 6)
obj.GUID = PlanetSideGUID(40)
val pkt = ObjectCreateMessage(
objDef.ObjectId,
obj.GUID,
objDef.Packet.ConstructorData(obj).get
)
"AvatarService" should {
"pass DeployItem" in {
service ! Service.Join("test")
service ! LocalServiceMessage("test", LocalAction.DeployItem(obj))
expectMsg(LocalServiceResponse("/test/Local", PlanetSideGUID(0), LocalResponse.SendResponse(pkt)))
}
}
}
class DeployableMapIconTest extends ActorTest {
ServiceManager.boot(system)