From 9708bf9bebc22622b84120b7fb0f715ac00bd091 Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Thu, 22 Aug 2024 23:33:47 -0400 Subject: [PATCH] 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 --- .../actor/service/AvatarServiceTest.scala | 23 - .../session/support/GeneralOperations.scala | 2 +- .../session/support/ZoningOperations.scala | 485 +++++++++--------- .../psforever/objects/BoomerDeployable.scala | 9 +- .../psforever/objects/OwnableByPlayer.scala | 11 +- .../objects/avatar/PlayerControl.scala | 40 +- .../objects/ce/DeployableBehavior.scala | 67 +-- .../converter/SmallTurretConverter.scala | 4 +- .../definition/converter/TRAPConverter.scala | 10 +- .../psforever/packet/GamePacketOpcode.scala | 2 +- .../game/GenericObjectAction2Message.scala | 33 ++ .../game/PlanetsideAttributeMessage.scala | 19 +- .../AegisShieldGeneratorData.scala | 5 +- .../BattleFrameRoboticsData.scala | 4 +- .../game/objectcreate/CaptureFlagData.scala | 2 +- .../CharacterAppearanceData.scala | 4 +- .../game/objectcreate/CharacterData.scala | 34 +- .../game/objectcreate/CommonFieldData.scala | 24 +- .../objectcreate/DetailedAmmoBoxData.scala | 2 +- .../objectcreate/DetailedCharacterData.scala | 22 +- .../DetailedConstructionToolData.scala | 2 +- .../objectcreate/DetailedPlayerData.scala | 12 +- .../game/objectcreate/DetailedREKData.scala | 2 +- .../objectcreate/DetailedWeaponData.scala | 18 +- .../game/objectcreate/DroppodData.scala | 4 +- .../game/objectcreate/HandheldData.scala | 2 +- .../game/objectcreate/InternalSlot.scala | 4 +- .../objectcreate/LargeDeployableData.scala | 4 +- .../objectcreate/LockerContainerData.scala | 6 +- .../game/objectcreate/ObjectClass.scala | 23 +- .../OneMannedFieldTurretData.scala | 5 +- .../objectcreate/OrbitalShuttleData.scala | 4 +- .../packet/game/objectcreate/PlayerData.scala | 16 +- .../packet/game/objectcreate/REKData.scala | 2 +- .../objectcreate/RemoteProjectileData.scala | 14 +- .../game/objectcreate/SmallTurretData.scala | 13 +- .../packet/game/objectcreate/TRAPData.scala | 13 +- .../game/objectcreate/VehicleData.scala | 10 +- .../packet/game/objectcreate/WeaponData.scala | 12 +- .../services/avatar/AvatarService.scala | 11 - .../avatar/AvatarServiceMessage.scala | 2 - .../services/local/LocalService.scala | 12 +- .../services/local/LocalServiceMessage.scala | 1 + .../GenericObjectAction2MessageTest.scala | 30 ++ .../objectcreate/SmallTurretDataTest.scala | 28 +- .../game/objectcreate/TRAPDataTest.scala | 6 +- .../objects/DeployableBehaviorTest.scala | 4 +- .../scala/objects/TelepadRouterTest.scala | 8 +- src/test/scala/service/LocalServiceTest.scala | 25 + 49 files changed, 593 insertions(+), 502 deletions(-) create mode 100644 src/main/scala/net/psforever/packet/game/GenericObjectAction2Message.scala create mode 100644 src/test/scala/game/GenericObjectAction2MessageTest.scala diff --git a/server/src/test/scala/actor/service/AvatarServiceTest.scala b/server/src/test/scala/actor/service/AvatarServiceTest.scala index 665c7ec7d..6e0eab30d 100644 --- a/server/src/test/scala/actor/service/AvatarServiceTest.scala +++ b/server/src/test/scala/actor/service/AvatarServiceTest.scala @@ -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") diff --git a/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala b/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala index 9f3961911..deea975f9 100644 --- a/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala @@ -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)) } /** diff --git a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala index 93ffd42f4..278bf91d4 100644 --- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala @@ -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 diff --git a/src/main/scala/net/psforever/objects/BoomerDeployable.scala b/src/main/scala/net/psforever/objects/BoomerDeployable.scala index 1679bbe36..6cf03ddb6 100644 --- a/src/main/scala/net/psforever/objects/BoomerDeployable.scala +++ b/src/main/scala/net/psforever/objects/BoomerDeployable.scala @@ -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 = { diff --git a/src/main/scala/net/psforever/objects/OwnableByPlayer.scala b/src/main/scala/net/psforever/objects/OwnableByPlayer.scala index 2653dca35..690dabe60 100644 --- a/src/main/scala/net/psforever/objects/OwnableByPlayer.scala +++ b/src/main/scala/net/psforever/objects/OwnableByPlayer.scala @@ -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)) diff --git a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala index 4245c7688..1d0746b5d 100644 --- a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala +++ b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala @@ -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, diff --git a/src/main/scala/net/psforever/objects/ce/DeployableBehavior.scala b/src/main/scala/net/psforever/objects/ce/DeployableBehavior.scala index d6a551a01..b363b63e3 100644 --- a/src/main/scala/net/psforever/objects/ce/DeployableBehavior.scala +++ b/src/main/scala/net/psforever/objects/ce/DeployableBehavior.scala @@ -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) + ) } } diff --git a/src/main/scala/net/psforever/objects/definition/converter/SmallTurretConverter.scala b/src/main/scala/net/psforever/objects/definition/converter/SmallTurretConverter.scala index 8bbb5249a..f27672d72 100644 --- a/src/main/scala/net/psforever/objects/definition/converter/SmallTurretConverter.scala +++ b/src/main/scala/net/psforever/objects/definition/converter/SmallTurretConverter.scala @@ -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) diff --git a/src/main/scala/net/psforever/objects/definition/converter/TRAPConverter.scala b/src/main/scala/net/psforever/objects/definition/converter/TRAPConverter.scala index 621fde9a3..a24527f13 100644 --- a/src/main/scala/net/psforever/objects/definition/converter/TRAPConverter.scala +++ b/src/main/scala/net/psforever/objects/definition/converter/TRAPConverter.scala @@ -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) diff --git a/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/src/main/scala/net/psforever/packet/GamePacketOpcode.scala index bea0c62eb..6bc06e6d3 100644 --- a/src/main/scala/net/psforever/packet/GamePacketOpcode.scala +++ b/src/main/scala/net/psforever/packet/GamePacketOpcode.scala @@ -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 diff --git a/src/main/scala/net/psforever/packet/game/GenericObjectAction2Message.scala b/src/main/scala/net/psforever/packet/game/GenericObjectAction2Message.scala new file mode 100644 index 000000000..728101d4f --- /dev/null +++ b/src/main/scala/net/psforever/packet/game/GenericObjectAction2Message.scala @@ -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] +} diff --git a/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala b/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala index a6fb90707..aaf908d14 100644 --- a/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala +++ b/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala @@ -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`
* `18 - CEP.`
* `19 - Anchors. Value is 0 to disengage, 1 to engage.`
- * `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)`
+ * `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)
*