diff --git a/src/main/scala/net/psforever/actors/session/AvatarActor.scala b/src/main/scala/net/psforever/actors/session/AvatarActor.scala index df4c7404..3936fa7e 100644 --- a/src/main/scala/net/psforever/actors/session/AvatarActor.scala +++ b/src/main/scala/net/psforever/actors/session/AvatarActor.scala @@ -13,7 +13,7 @@ import net.psforever.objects.avatar.scoring.{Assist, Death, EquipmentStat, KDASt import net.psforever.objects.sourcing.{TurretSource, VehicleSource} import net.psforever.packet.game.ImplantAction import net.psforever.services.avatar.AvatarServiceResponse -import net.psforever.types.{ChatMessageType, StatisticalCategory, StatisticalElement, Vector3} +import net.psforever.types.{ChatMessageType, StatisticalCategory, StatisticalElement} import net.psforever.zones.Zones import org.joda.time.{LocalDateTime, Seconds} diff --git a/src/main/scala/net/psforever/actors/session/normal/LocalHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/normal/LocalHandlerLogic.scala index 009d0bbd..09bd7857 100644 --- a/src/main/scala/net/psforever/actors/session/normal/LocalHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/LocalHandlerLogic.scala @@ -7,7 +7,7 @@ import net.psforever.objects.ce.Deployable import net.psforever.objects.serverobject.doors.Door import net.psforever.objects.vehicles.MountableWeapons import net.psforever.objects.{BoomerDeployable, ExplosiveDeployable, TelepadDeployable, Tool, TurretDeployable} -import net.psforever.packet.game.{ChatMsg, DeployableObjectsInfoMessage, GenericActionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HackMessage, HackState, InventoryStateMessage, ObjectAttachMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectDetachMessage, OrbitalShuttleTimeMsg, PadAndShuttlePair, PlanetsideAttributeMessage, ProximityTerminalUseMessage, SetEmpireMessage, TriggerEffectMessage, TriggerSoundMessage, TriggeredSound, VehicleStateMessage} +import net.psforever.packet.game.{ChatMsg, DeployableObjectsInfoMessage, GenericActionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HackMessage, HackState, HackState1, InventoryStateMessage, ObjectAttachMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectDetachMessage, OrbitalShuttleTimeMsg, PadAndShuttlePair, PlanetsideAttributeMessage, ProximityTerminalUseMessage, SetEmpireMessage, TriggerEffectMessage, TriggerSoundMessage, TriggeredSound, VehicleStateMessage} import net.psforever.services.Service import net.psforever.services.local.LocalResponse import net.psforever.types.{ChatMessageType, PlanetSideGUID, Vector3} @@ -136,7 +136,7 @@ class LocalHandlerLogic(val ops: SessionLocalHandlers, implicit val context: Act DeconstructDeployable(obj, dguid, pos, obj.Orientation, effect) case LocalResponse.SendHackMessageHackCleared(targetGuid, unk1, unk2) => - sendResponse(HackMessage(unk1=0, targetGuid, guid, progress=0, unk1, HackState.HackCleared, unk2)) + sendResponse(HackMessage(HackState1.Unk0, targetGuid, guid, progress=0, unk1.toFloat, HackState.HackCleared, unk2)) case LocalResponse.HackObject(targetGuid, unk1, unk2) => sessionLogic.general.hackObject(targetGuid, unk1, unk2) diff --git a/src/main/scala/net/psforever/actors/session/spectator/LocalHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/spectator/LocalHandlerLogic.scala index c0810ab6..f28ab0f3 100644 --- a/src/main/scala/net/psforever/actors/session/spectator/LocalHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/spectator/LocalHandlerLogic.scala @@ -7,7 +7,7 @@ import net.psforever.objects.ce.Deployable import net.psforever.objects.guid.{GUIDTask, TaskWorkflow} import net.psforever.objects.vehicles.MountableWeapons import net.psforever.objects.{BoomerDeployable, ExplosiveDeployable, TelepadDeployable, Tool, TurretDeployable} -import net.psforever.packet.game.{ChatMsg, DeployableObjectsInfoMessage, GenericActionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HackMessage, HackState, InventoryStateMessage, ObjectAttachMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectDetachMessage, OrbitalShuttleTimeMsg, PadAndShuttlePair, PlanetsideAttributeMessage, ProximityTerminalUseMessage, SetEmpireMessage, TriggerEffectMessage, TriggerSoundMessage, TriggeredSound, VehicleStateMessage} +import net.psforever.packet.game.{ChatMsg, DeployableObjectsInfoMessage, GenericActionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HackMessage, HackState, HackState1, InventoryStateMessage, ObjectAttachMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectDetachMessage, OrbitalShuttleTimeMsg, PadAndShuttlePair, PlanetsideAttributeMessage, ProximityTerminalUseMessage, SetEmpireMessage, TriggerEffectMessage, TriggerSoundMessage, TriggeredSound, VehicleStateMessage} import net.psforever.services.Service import net.psforever.services.local.LocalResponse import net.psforever.types.{ChatMessageType, PlanetSideGUID, Vector3} @@ -125,7 +125,7 @@ class LocalHandlerLogic(val ops: SessionLocalHandlers, implicit val context: Act DeconstructDeployable(obj, dguid, pos, obj.Orientation, effect) case LocalResponse.SendHackMessageHackCleared(targetGuid, unk1, unk2) => - sendResponse(HackMessage(unk1=0, targetGuid, guid, progress=0, unk1, HackState.HackCleared, unk2)) + sendResponse(HackMessage(HackState1.Unk0, targetGuid, guid, progress=0, unk1.toFloat, HackState.HackCleared, unk2)) case LocalResponse.HackObject(targetGuid, unk1, unk2) => sessionLogic.general.hackObject(targetGuid, unk1, unk2) 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 26d1db69..9f396191 100644 --- a/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala @@ -562,8 +562,8 @@ class GeneralOperations( * @param unk1 na * @param unk2 na */ - def hackObject(targetGuid: PlanetSideGUID, unk1: Long, unk2: Long): Unit = { - sendResponse(HackMessage(unk1=0, targetGuid, player_guid=Service.defaultPlayerGUID, progress=100, unk1, HackState.Hacked, unk2)) + def hackObject(targetGuid: PlanetSideGUID, unk1: Long, unk2: HackState7): Unit = { + sendResponse(HackMessage(HackState1.Unk0, targetGuid, player_guid=Service.defaultPlayerGUID, progress=100, unk1, 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 6648cf3b..93ffd42f 100644 --- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala @@ -17,7 +17,7 @@ import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects.serverobject.turret.auto.AutomatedTurret import net.psforever.objects.sourcing.{PlayerSource, SourceEntry, VehicleSource} import net.psforever.objects.vital.{InGameHistory, IncarnationActivity, ReconstructionActivity, SpawningActivity} -import net.psforever.packet.game.{CampaignStatistic, ChangeFireStateMessage_Start, MailMessage, ObjectDetectedMessage, SessionStatistic} +import net.psforever.packet.game.{CampaignStatistic, ChangeFireStateMessage_Start, HackState7, MailMessage, ObjectDetectedMessage, SessionStatistic} import net.psforever.services.chat.DefaultChannel import scala.collection.mutable @@ -1084,26 +1084,30 @@ class ZoningOperations( * @param zone the zone being loaded */ def configZone(zone: Zone): Unit = { - zone.Buildings.values.foreach(building => { - val guid = building.GUID - sendResponse(SetEmpireMessage(guid, building.Faction)) - // power - building.Generator match { - case Some(obj) if obj.Condition == PlanetSideGeneratorState.Destroyed || building.NtuLevel == 0 => - sendResponse(PlanetsideAttributeMessage(guid, 48, 1)) //amenities disabled; red warning lights - sendResponse(PlanetsideAttributeMessage(guid, 38, 0)) //disable spawn target on deployment map - case _ => () + zone + .Buildings + .values + .foreach { building => + val guid = building.GUID + sendResponse(SetEmpireMessage(guid, building.Faction)) + // power + building.Generator match { + case Some(obj) if obj.Condition == PlanetSideGeneratorState.Destroyed || building.NtuLevel == 0 => + sendResponse(PlanetsideAttributeMessage(guid, 48, 1)) //amenities disabled; red warning lights + sendResponse(PlanetsideAttributeMessage(guid, 38, 0)) //disable spawn target on deployment map + case _ => () + } + // capitol force dome state + if (building.IsCapitol && building.ForceDomeActive) { + sendResponse(GenericObjectActionMessage(guid, 13)) + } + // amenities + building.Amenities.collect { + case obj if obj.Destroyed => configAmenityAsDestroyed(obj) + case obj => configAmenityAsWorking(obj) + } + //sendResponse(HackMessage(HackState1.Unk3, guid, Service.defaultPlayerGUID, progress=0, -1f, HackState.HackCleared, HackState7.Unk8)) } - // capitol force dome state - if (building.IsCapitol && building.ForceDomeActive) { - sendResponse(GenericObjectActionMessage(guid, 13)) - } - // amenities - building.Amenities.collect { - case obj if obj.Destroyed => configAmenityAsDestroyed(obj) - case obj => configAmenityAsWorking(obj) - } - }) } /** @@ -1156,7 +1160,7 @@ class ZoningOperations( HackCaptureActor.GetHackUpdateAttributeValue(amenity.asInstanceOf[CaptureTerminal], isResecured = false) ) case _ => - sessionLogic.general.hackObject(amenity.GUID, 1114636288L, 8L) //generic hackable object + sessionLogic.general.hackObject(amenity.GUID, unk1 = 1114636288L, HackState7.Unk8) //generic hackable object } // sync capture flags diff --git a/src/main/scala/net/psforever/objects/FieldTurretDeployable.scala b/src/main/scala/net/psforever/objects/FieldTurretDeployable.scala index 4d894c2a..1745f647 100644 --- a/src/main/scala/net/psforever/objects/FieldTurretDeployable.scala +++ b/src/main/scala/net/psforever/objects/FieldTurretDeployable.scala @@ -11,6 +11,7 @@ import net.psforever.objects.serverobject.turret.{MountableTurret, WeaponTurrets import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} import net.psforever.objects.sourcing.SourceEntry import net.psforever.objects.vital.{InGameActivity, ShieldCharge} +import net.psforever.packet.game.HackState1 import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} import net.psforever.types.PlanetSideGUID @@ -54,7 +55,7 @@ class FieldTurretControl(turret: TurretDeployable) sender() ! CommonMessages.Progress( GenericHackables.GetHackSpeed(player, turret), WeaponTurrets.FinishHackingTurretDeployable(turret, player), - GenericHackables.HackingTickAction(progressType = 1, player, turret, item.GUID) + GenericHackables.HackingTickAction(HackState1.Unk1, player, turret, item.GUID) ) case CommonMessages.ChargeShields(amount, motivator) => diff --git a/src/main/scala/net/psforever/objects/Vehicles.scala b/src/main/scala/net/psforever/objects/Vehicles.scala index c25073e0..086d7d04 100644 --- a/src/main/scala/net/psforever/objects/Vehicles.scala +++ b/src/main/scala/net/psforever/objects/Vehicles.scala @@ -10,7 +10,7 @@ import net.psforever.objects.serverobject.transfer.TransferContainer import net.psforever.objects.serverobject.structures.WarpGate import net.psforever.objects.vehicles._ import net.psforever.objects.zones.Zone -import net.psforever.packet.game.TriggeredSound +import net.psforever.packet.game.{HackMessage, HackState, HackState1, HackState7, TriggeredSound} import net.psforever.types.{DriveState, PlanetSideEmpire, PlanetSideGUID, Vector3} import net.psforever.services.Service import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} @@ -73,7 +73,7 @@ object Vehicles { } vehicle.AssignOwnership(None) val empire = VehicleLockState.Empire.id - val factionChannel = s"${vehicle.Faction}" + //val factionChannel = s"${vehicle.Faction}" (0 to 2).foreach(group => { vehicle.PermissionGroup(group, empire) /*vehicle.Zone.VehicleEvents ! VehicleServiceMessage( @@ -131,7 +131,7 @@ object Vehicles { if (vehicle.OwnerGuid.contains(pguid)) { vehicle.AssignOwnership(None) //vehicle.Zone.VehicleEvents ! VehicleServiceMessage(player.Name, VehicleAction.Ownership(pguid, PlanetSideGUID(0))) - val vguid = vehicle.GUID + //val vguid = vehicle.GUID val empire = VehicleLockState.Empire.id (0 to 2).foreach(group => { vehicle.PermissionGroup(group, empire) @@ -230,11 +230,23 @@ object Vehicles { * Change the faction of the vehicle to the hacker's faction and remove all occupants. * @param target The `Vehicle` object that has been hacked/jacked * @param hacker the one whoi performed the hack and will inherit ownership of the target vehicle - * @param unk na; used by `HackMessage` as `unk5` */ - def FinishHackingVehicle(target: Vehicle, hacker: Player, unk: Long)(): Unit = { + def FinishHackingVehicle(target: Vehicle, hacker: Player)(): Unit = { log.info(s"${hacker.Name} has jacked a ${target.Definition.Name}") + val tGuid = target.GUID + val hGuid = hacker.GUID + val hFaction = hacker.Faction val zone = target.Zone + val zoneid = zone.id + val vehicleEvents = zone.VehicleEvents + vehicleEvents ! VehicleServiceMessage( + zoneid, + VehicleAction.SendResponse( + Service.defaultPlayerGUID, + HackMessage(HackState1.Unk2, tGuid, hGuid, 100, 0f, HackState.Hacked, HackState7.Unk8) + ) + ) + target.Actor ! CommonMessages.Hack(hacker, target) // Forcefully dismount any cargo target.CargoHolds.foreach { case (_, cargoHold) => cargoHold.occupant.collect { @@ -244,18 +256,16 @@ object Vehicles { // Forcefully dismount all seated occupants from the vehicle target.Seats.values.foreach(seat => { seat.occupant.collect { - tplayer: Player => - seat.unmount(tplayer) - tplayer.VehicleSeated = None - if (tplayer.HasGUID) { - zone.VehicleEvents ! VehicleServiceMessage( - zone.id, - VehicleAction.KickPassenger(tplayer.GUID, 4, unk2 = false, target.GUID) - ) - } + player: Player => + seat.unmount(player) + player.VehicleSeated = None + vehicleEvents ! VehicleServiceMessage( + zoneid, + VehicleAction.KickPassenger(player.GUID, 4, unk2 = false, tGuid) + ) } }) - // If the vehicle can fly and is flying deconstruct it, and well played to whomever managed to hack a plane in mid air. I'm impressed. + // If the vehicle can fly and is flying: deconstruct it; and well played to whomever managed to hack a plane in mid air if (target.Definition.CanFly && target.isFlying) { // todo: Should this force the vehicle to land in the same way as when a pilot bails with passengers on board? target.Actor ! Vehicle.Deconstruct() @@ -273,19 +283,18 @@ object Vehicles { Vehicles.Disown(tplayer, target) } // Now take ownership of the jacked vehicle - target.Actor ! CommonMessages.Hack(hacker, target) - target.Faction = hacker.Faction + target.Faction = hFaction Vehicles.Own(target, hacker) //todo: Send HackMessage -> HackCleared to vehicle? can be found in packet captures. Not sure if necessary. // And broadcast the faction change to other clients zone.AvatarEvents ! AvatarServiceMessage( - zone.id, - AvatarAction.SetEmpire(Service.defaultPlayerGUID, target.GUID, hacker.Faction) + zoneid, + AvatarAction.SetEmpire(Service.defaultPlayerGUID, tGuid, hFaction) ) } zone.LocalEvents ! LocalServiceMessage( - zone.id, - LocalAction.TriggerSound(hacker.GUID, TriggeredSound.HackVehicle, target.Position, 30, 0.49803925f) + zoneid, + LocalAction.TriggerSound(hGuid, TriggeredSound.HackVehicle, target.Position, 30, 0.49803925f) ) // Clean up after specific vehicles, e.g. remove router telepads // If AMS is deployed, swap it to the new faction @@ -298,9 +307,17 @@ object Vehicles { util.Actor ! TelepadLike.Activate(util) } case GlobalDefinitions.ams if target.DeploymentState == DriveState.Deployed => - zone.VehicleEvents ! VehicleServiceMessage.AMSDeploymentChange(zone) + vehicleEvents ! VehicleServiceMessage.AMSDeploymentChange(zone) case _ => () } + vehicleEvents ! VehicleServiceMessage( + zoneid, + VehicleAction.SendResponse( + Service.defaultPlayerGUID, + HackMessage(HackState1.Unk2, tGuid, tGuid, 0, 1L, HackState.HackCleared, HackState7.Unk8) + ) + ) + target.Actor ! CommonMessages.ClearHack() } def FindANTChargingSource( diff --git a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala index 41b224ad..11059fba 100644 --- a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala +++ b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala @@ -165,7 +165,7 @@ object GlobalDefinitionsMiscellaneous { cert_terminal.Geometry = GeometryForm.representByCylinder(radius = 0.66405f, height = 1.09374f) implant_terminal_mech.Name = "implant_terminal_mech" - implant_terminal_mech.MaxHealth = 1500 //TODO 1000; right now, 1000 (mech) + 500 (interface) + implant_terminal_mech.MaxHealth = 1000 implant_terminal_mech.Damageable = true implant_terminal_mech.Repairable = true implant_terminal_mech.autoRepair = AutoRepairStats(1.6f, 5000, 2400, 0.05f) @@ -176,7 +176,7 @@ object GlobalDefinitionsMiscellaneous { implant_terminal_interface.Name = "implant_terminal_interface" implant_terminal_interface.Tab += 0 -> ImplantPage(ImplantTerminalDefinition.implants) implant_terminal_interface.MaxHealth = 500 - implant_terminal_interface.Damageable = false //TODO true + implant_terminal_interface.Damageable = false //true implant_terminal_interface.Repairable = true implant_terminal_interface.autoRepair = AutoRepairStats(1, 5000, 200, 1) implant_terminal_interface.RepairIfDestroyed = true diff --git a/src/main/scala/net/psforever/objects/serverobject/environment/interaction/common/WithDeath.scala b/src/main/scala/net/psforever/objects/serverobject/environment/interaction/common/WithDeath.scala index 4e389884..4e63d45b 100644 --- a/src/main/scala/net/psforever/objects/serverobject/environment/interaction/common/WithDeath.scala +++ b/src/main/scala/net/psforever/objects/serverobject/environment/interaction/common/WithDeath.scala @@ -6,7 +6,7 @@ import net.psforever.objects.serverobject.environment.{EnvironmentAttribute, Env import net.psforever.objects.sourcing.SourceEntry import net.psforever.objects.vital.etc.SuicideReason import net.psforever.objects.vital.interaction.DamageInteraction -import net.psforever.objects.vital.{IncarnationActivity, ReconstructionActivity, Vitality} +import net.psforever.objects.vital.{IncarnationActivity, Vitality} import net.psforever.objects.zones.InteractsWithZone import scala.annotation.unused diff --git a/src/main/scala/net/psforever/objects/serverobject/hackable/GenericHackables.scala b/src/main/scala/net/psforever/objects/serverobject/hackable/GenericHackables.scala index a88b1907..3511b4dc 100644 --- a/src/main/scala/net/psforever/objects/serverobject/hackable/GenericHackables.scala +++ b/src/main/scala/net/psforever/objects/serverobject/hackable/GenericHackables.scala @@ -3,7 +3,7 @@ package net.psforever.objects.serverobject.hackable import net.psforever.objects.{Player, Vehicle} import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} -import net.psforever.packet.game.{HackMessage, HackState} +import net.psforever.packet.game.{HackMessage, HackState, HackState1, HackState7} import net.psforever.types.PlanetSideGUID import net.psforever.services.Service import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} @@ -68,43 +68,34 @@ object GenericHackables { * @see `HackState` * @param progressType 1 - remote electronics kit hack (various ...); * 2 - nano dispenser (upgrade canister) turret upgrade - * @param tplayer the player performing the action + * @param hacker the player performing the action * @param target the object being affected * @param tool_guid the tool being used to affest the object * @param progress the current progress value * @return `true`, if the next cycle of progress should occur; * `false`, otherwise */ - def HackingTickAction(progressType: Int, tplayer: Player, target: PlanetSideServerObject, tool_guid: PlanetSideGUID)( + def HackingTickAction(progressType: HackState1, hacker: Player, target: PlanetSideServerObject, tool_guid: PlanetSideGUID)( progress: Float ): Boolean = { //hack state for progress bar visibility - val vis = if (progress <= 0L) { - HackState.Start + val (progressState, progressGrade) = if (progress <= 0L) { + (HackState.Start, 0) } else if (progress >= 100L) { - HackState.Finished - } else if (target.isMoving(test = 1f)) { - // If the object is moving (more than slightly to account for things like magriders rotating, or the last velocity reported being the magrider dipping down on dismount) then cancel the hack - HackState.Cancelled + (HackState.Finished, 100) + } else if (target.isMoving(test = 1f) || target.Destroyed || !target.HasGUID) { + (HackState.Cancelled, 0) } else { - HackState.Ongoing + (HackState.Ongoing, progress.toInt) } target.Zone.AvatarEvents ! AvatarServiceMessage( - tplayer.Name, + hacker.Name, AvatarAction.SendResponse( Service.defaultPlayerGUID, - if (!target.HasGUID) { - //cancel the hack (target is gone) - HackMessage(progressType, target.GUID, tplayer.GUID, 0, 0L, HackState.Cancelled, 8L) - } else if (vis == HackState.Cancelled) { - //cancel the hack (e.g. vehicle drove away) - HackMessage(progressType, target.GUID, tplayer.GUID, 0, 0L, vis, 8L) - } else { - HackMessage(progressType, target.GUID, tplayer.GUID, progress.toInt, 0L, vis, 8L) - } + HackMessage(progressType, target.GUID, hacker.GUID, progressGrade, 0L, progressState, HackState7.Unk8) ) ) - vis != HackState.Cancelled + progressState != HackState.Cancelled } /** @@ -112,12 +103,12 @@ object GenericHackables { * Pass the message onto the hackable object and onto the local events system. * @param target the `Hackable` object that has been hacked * @param user the player that is performing this hacking task - * @param unk na; - * used by `HackMessage` as `unk5` + * @param hackValue na; + * @param hackClearValue na * @see `HackMessage` */ //TODO add params here depending on which params in HackMessage are important - def FinishHacking(target: PlanetSideServerObject with Hackable, user: Player, unk: Long)(): Unit = { + def FinishHacking(target: PlanetSideServerObject with Hackable, user: Player, hackValue: Int, hackClearValue: Int)(): Unit = { import akka.pattern.ask import scala.concurrent.duration._ // Wait for the target actor to set the HackedBy property, otherwise LocalAction.HackTemporarily will not complete properly @@ -138,7 +129,7 @@ object GenericHackables { zone.LocalEvents ! LocalServiceMessage( zoneId, LocalAction - .HackTemporarily(pguid, zone, target, unk, target.HackEffectDuration(user.avatar.hackingSkillLevel())) + .HackTemporarily(pguid, zone, target, hackValue, hackClearValue, target.HackEffectDuration(user.avatar.hackingSkillLevel())) ) case Failure(_) => log.warn(s"Hack message failed on target: ${target.Definition.Name}@${target.GUID.guid}") diff --git a/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockControl.scala b/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockControl.scala index 026f3f1f..e9249e10 100644 --- a/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockControl.scala @@ -4,9 +4,10 @@ package net.psforever.objects.serverobject.locks import akka.actor.Actor import net.psforever.objects.{GlobalDefinitions, SimpleItem} import net.psforever.objects.serverobject.CommonMessages -import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} +import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior import net.psforever.objects.serverobject.hackable.{GenericHackables, HackableBehavior} import net.psforever.objects.serverobject.structures.Building +import net.psforever.packet.game.HackState1 import net.psforever.types.{PlanetSideEmpire, Vector3} /** @@ -18,8 +19,8 @@ class IFFLockControl(lock: IFFLock) extends Actor with FactionAffinityBehavior.Check with HackableBehavior.GenericHackable { - def FactionObject: FactionAffinity = lock - def HackableObject = lock + def FactionObject: IFFLock = lock + def HackableObject: IFFLock = lock def receive: Receive = checkBehavior @@ -28,16 +29,17 @@ class IFFLockControl(lock: IFFLock) case CommonMessages.Use(player, Some(item: SimpleItem)) if item.Definition == GlobalDefinitions.remote_electronics_kit => if (lock.Faction != player.Faction) { + // 300 / 1 sender() ! CommonMessages.Progress( GenericHackables.GetHackSpeed(player, lock), - GenericHackables.FinishHacking(lock, player, 1114636288L), - GenericHackables.HackingTickAction(progressType = 1, player, lock, item.GUID) + GenericHackables.FinishHacking(lock, player, hackValue = 60, hackClearValue = 60), + GenericHackables.HackingTickAction(HackState1.Unk1, player, lock, item.GUID) ) } else if (lock.Faction == player.Faction && lock.HackedBy.nonEmpty) { sender() ! CommonMessages.Progress( GenericHackables.GetHackSpeed(player, lock), IFFLocks.FinishResecuringIFFLock(lock), - GenericHackables.HackingTickAction(progressType = 1, player, lock, item.GUID) + GenericHackables.HackingTickAction(HackState1.Unk1, player, lock, item.GUID) ) } else { val log = org.log4s.getLogger diff --git a/src/main/scala/net/psforever/objects/serverobject/mblocker/LockerControl.scala b/src/main/scala/net/psforever/objects/serverobject/mblocker/LockerControl.scala index 1ba6ffd2..6d1305d4 100644 --- a/src/main/scala/net/psforever/objects/serverobject/mblocker/LockerControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/mblocker/LockerControl.scala @@ -4,8 +4,9 @@ package net.psforever.objects.serverobject.mblocker import akka.actor.Actor import net.psforever.objects.{GlobalDefinitions, SimpleItem} import net.psforever.objects.serverobject.CommonMessages -import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} +import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior import net.psforever.objects.serverobject.hackable.{GenericHackables, HackableBehavior} +import net.psforever.packet.game.HackState1 /** * An `Actor` that handles messages being dispatched to a specific `Locker`. @@ -15,8 +16,8 @@ class LockerControl(locker: Locker) extends Actor with FactionAffinityBehavior.Check with HackableBehavior.GenericHackable { - def FactionObject: FactionAffinity = locker - def HackableObject = locker + def FactionObject: Locker = locker + def HackableObject: Locker = locker def receive: Receive = checkBehavior @@ -28,8 +29,8 @@ class LockerControl(locker: Locker) if (locker.Faction != player.Faction && locker.HackedBy.isEmpty) { sender() ! CommonMessages.Progress( GenericHackables.GetHackSpeed(player, locker), - GenericHackables.FinishHacking(locker, player, 3212836864L), - GenericHackables.HackingTickAction(progressType = 1, player, locker, item.GUID) + GenericHackables.FinishHacking(locker, player, hackValue = -1, hackClearValue = -1), + GenericHackables.HackingTickAction(HackState1.Unk1, player, locker, item.GUID) ) } case _ => ; diff --git a/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala b/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala index adea8ef2..9e5e1b34 100644 --- a/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala @@ -3,6 +3,7 @@ package net.psforever.objects.serverobject.terminals import akka.actor.{ActorRef, Cancellable} import net.psforever.objects.sourcing.AmenitySource +import net.psforever.packet.game.HackState1 import org.log4s.Logger import scala.annotation.unused @@ -73,8 +74,8 @@ class ProximityTerminalControl(term: Terminal with ProximityUnit) case b: Building if (b.Faction != player.Faction || b.CaptureTerminalIsHacked) && term.HackedBy.isEmpty => sender() ! CommonMessages.Progress( GenericHackables.GetHackSpeed(player, term), - GenericHackables.FinishHacking(term, player, 3212836864L), - GenericHackables.HackingTickAction(progressType = 1, player, term, item.GUID) + GenericHackables.FinishHacking(term, player, hackValue = -1, hackClearValue = -1), + GenericHackables.HackingTickAction(HackState1.Unk1, player, term, item.GUID) ) case _ => ; } diff --git a/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala b/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala index 46f596aa..94c340fd 100644 --- a/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala @@ -11,6 +11,7 @@ import net.psforever.objects.serverobject.hackable.{GenericHackables, HackableBe import net.psforever.objects.serverobject.repair.{AmenityAutoRepair, RepairableAmenity} import net.psforever.objects.serverobject.structures.{Building, PoweredAmenityControl} import net.psforever.objects.vital.interaction.DamageResult +import net.psforever.packet.game.HackState1 import net.psforever.services.Service import net.psforever.services.local.{LocalAction, LocalServiceMessage} @@ -48,10 +49,11 @@ class TerminalControl(term: Terminal) //TODO setup certifications check term.Owner match { case b: Building if (b.Faction != player.Faction || b.CaptureTerminalIsHacked) && term.HackedBy.isEmpty => + //order terminals are 90 / 1, or 60 / ? sender() ! CommonMessages.Progress( GenericHackables.GetHackSpeed(player, term), - GenericHackables.FinishHacking(term, player, 3212836864L), - GenericHackables.HackingTickAction(progressType = 1, player, term, item.GUID) + GenericHackables.FinishHacking(term, player, hackValue = -1, hackClearValue = -1), + GenericHackables.HackingTickAction(HackState1.Unk1, player, term, item.GUID) ) case _ => () } diff --git a/src/main/scala/net/psforever/objects/serverobject/terminals/capture/CaptureTerminalAwareBehavior.scala b/src/main/scala/net/psforever/objects/serverobject/terminals/capture/CaptureTerminalAwareBehavior.scala index f12a2e3b..fc9fda53 100644 --- a/src/main/scala/net/psforever/objects/serverobject/terminals/capture/CaptureTerminalAwareBehavior.scala +++ b/src/main/scala/net/psforever/objects/serverobject/terminals/capture/CaptureTerminalAwareBehavior.scala @@ -1,9 +1,7 @@ package net.psforever.objects.serverobject.terminals.capture import akka.actor.Actor.Receive -import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.structures.Amenity -import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} import scala.annotation.unused @@ -24,28 +22,7 @@ trait CaptureTerminalAwareBehavior { protected def captureTerminalIsResecured(@unused terminal: CaptureTerminal): Unit = { /* intentionally blank */ } - protected def captureTerminalIsHacked(@unused terminal: CaptureTerminal): Unit = { - // Remove seated occupants for mountables - CaptureTerminalAwareObject match { - case mountable: Mountable => - val guid = mountable.GUID - val zone = mountable.Zone - val zoneId = zone.id - val events = zone.VehicleEvents - mountable.Seats.values.zipWithIndex.foreach { - case (seat, seat_num) => - seat.occupant.collect { - case player => - seat.unmount(player) - player.VehicleSeated = None - if (player.HasGUID) { - events ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(player.GUID, seat_num, unk2=true, guid)) - } - } - } - case _ => () - } - } + protected def captureTerminalIsHacked(@unused terminal: CaptureTerminal): Unit = { /* intentionally blank */ } } object CaptureTerminalAwareBehavior { diff --git a/src/main/scala/net/psforever/objects/serverobject/terminals/capture/CaptureTerminalControl.scala b/src/main/scala/net/psforever/objects/serverobject/terminals/capture/CaptureTerminalControl.scala index 973f2d74..f5dc91a7 100644 --- a/src/main/scala/net/psforever/objects/serverobject/terminals/capture/CaptureTerminalControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/terminals/capture/CaptureTerminalControl.scala @@ -3,16 +3,17 @@ package net.psforever.objects.serverobject.terminals.capture import akka.actor.Actor import net.psforever.objects.serverobject.CommonMessages -import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} +import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior import net.psforever.objects.serverobject.hackable.{GenericHackables, HackableBehavior} import net.psforever.objects.{GlobalDefinitions, SimpleItem} +import net.psforever.packet.game.HackState1 class CaptureTerminalControl(terminal: CaptureTerminal) extends Actor with FactionAffinityBehavior.Check with HackableBehavior.GenericHackable { - def FactionObject: FactionAffinity = terminal - def HackableObject = terminal + def FactionObject: CaptureTerminal = terminal + def HackableObject: CaptureTerminal = terminal def receive: Receive = checkBehavior @@ -27,11 +28,11 @@ class CaptureTerminalControl(terminal: CaptureTerminal) if (canHack) { sender() ! CommonMessages.Progress( GenericHackables.GetHackSpeed(player, terminal), - CaptureTerminals.FinishHackingCaptureConsole(terminal, player, 3212836864L), - GenericHackables.HackingTickAction(progressType = 1, player, terminal, item.GUID) + CaptureTerminals.FinishHackingCaptureConsole(terminal, player, unk = -1), + GenericHackables.HackingTickAction(HackState1.Unk1, player, terminal, item.GUID) ) } - case _ => ; //no default message + case _ => () //no default message } } diff --git a/src/main/scala/net/psforever/objects/serverobject/terminals/capture/CaptureTerminals.scala b/src/main/scala/net/psforever/objects/serverobject/terminals/capture/CaptureTerminals.scala index 7144d480..399d45d8 100644 --- a/src/main/scala/net/psforever/objects/serverobject/terminals/capture/CaptureTerminals.scala +++ b/src/main/scala/net/psforever/objects/serverobject/terminals/capture/CaptureTerminals.scala @@ -20,16 +20,16 @@ object CaptureTerminals {import scala.concurrent.duration._ * @see `HackMessage` */ //TODO add params here depending on which params in HackMessage are important - def FinishHackingCaptureConsole(target: CaptureTerminal, hackingPlayer: Player, unk: Long)(): Unit = { + def FinishHackingCaptureConsole(target: CaptureTerminal, hackingPlayer: Player, unk: Int)(): Unit = { import akka.pattern.ask - log.info(s"${hackingPlayer.toString} hacked a ${target.Definition.Name}") // Wait for the target actor to set the HackedBy property import scala.concurrent.ExecutionContext.Implicits.global ask(target.Actor, CommonMessages.Hack(hackingPlayer, target))(timeout = 2 second) .mapTo[CommonMessages.EntityHackState] .onComplete { case Success(_) => + log.info(s"${hackingPlayer.toString} hacked a ${target.Definition.Name}") val zone = target.Zone val zoneid = zone.id val events = zone.LocalEvents diff --git a/src/main/scala/net/psforever/objects/serverobject/terminals/implant/ImplantInterfaceControl.scala b/src/main/scala/net/psforever/objects/serverobject/terminals/implant/ImplantInterfaceControl.scala new file mode 100644 index 00000000..e6dea2ad --- /dev/null +++ b/src/main/scala/net/psforever/objects/serverobject/terminals/implant/ImplantInterfaceControl.scala @@ -0,0 +1,39 @@ +// Copyright (c) 2024 PSForever +package net.psforever.objects.serverobject.terminals.implant + +import akka.actor.ActorRef +import net.psforever.objects.Player +import net.psforever.objects.serverobject.hackable.{GenericHackables, Hackable} +import net.psforever.objects.serverobject.structures.Amenity +import net.psforever.objects.serverobject.terminals.{Terminal, TerminalControl} +import net.psforever.objects.zones.Zone +import net.psforever.types.PlanetSideGUID + +object ImplantInterfaceControl { + private def FindPairedTerminalMech( + zone: Zone, + interfaceGuid: PlanetSideGUID + ): Option[Amenity with Hackable] = { + zone + .map + .terminalToInterface + .find { case (_, guid) => guid == interfaceGuid.guid } + .flatMap { case (mechGuid, _) => zone.GUID(mechGuid) } + .collect { case mech: ImplantTerminalMech if !mech.Destroyed && mech.HackedBy.isEmpty => mech } + } +} + +class ImplantInterfaceControl(private val terminal: Terminal) + extends TerminalControl(terminal) { + + override def performHack(player: Player, data: Option[Any], replyTo: ActorRef): Unit = { + HackableObject.HackedBy + .orElse { + super.performHack(player, data, replyTo) + ImplantInterfaceControl + .FindPairedTerminalMech(terminal.Zone, terminal.GUID) + .foreach(GenericHackables.FinishHacking(_, player, hackValue = -1, hackClearValue = -1)()) + None + } + } +} diff --git a/src/main/scala/net/psforever/objects/serverobject/terminals/implant/ImplantTerminalInterface.scala b/src/main/scala/net/psforever/objects/serverobject/terminals/implant/ImplantTerminalInterface.scala new file mode 100644 index 00000000..5d4b18af --- /dev/null +++ b/src/main/scala/net/psforever/objects/serverobject/terminals/implant/ImplantTerminalInterface.scala @@ -0,0 +1,25 @@ +// Copyright (c) 2024 PSForever +package net.psforever.objects.serverobject.terminals.implant + +import akka.actor.ActorContext +import net.psforever.objects.serverobject.terminals.{Terminal, TerminalDefinition} +import net.psforever.types.Vector3 + +object ImplantTerminalInterface { + /** + * Instantiate and configure a `Terminal` object + * @param pdef `ObjectDefinition` that constructs this object and maintains some of its immutable fields + * @param pos position + * @param id the unique id that will be assigned to this entity + * @param context a context to allow the object to properly set up `ActorSystem` functionality + * @return the `Terminal` object + */ + def Constructor(pos: Vector3, pdef: TerminalDefinition)(id: Int, context: ActorContext): Terminal = { + import akka.actor.Props + + val obj = Terminal(pdef) + obj.Position = pos + obj.Actor = context.actorOf(Props(classOf[ImplantInterfaceControl], obj), s"${obj.Definition.Name}_$id") + obj + } +} diff --git a/src/main/scala/net/psforever/objects/serverobject/terminals/implant/ImplantTerminalMechControl.scala b/src/main/scala/net/psforever/objects/serverobject/terminals/implant/ImplantTerminalMechControl.scala index f4f53b66..c03709e6 100644 --- a/src/main/scala/net/psforever/objects/serverobject/terminals/implant/ImplantTerminalMechControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/terminals/implant/ImplantTerminalMechControl.scala @@ -1,25 +1,47 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.terminals.implant +import akka.actor.ActorRef import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior import net.psforever.objects.serverobject.damage.Damageable.Target import net.psforever.objects.serverobject.damage.{Damageable, DamageableEntity, DamageableMountable} -import net.psforever.objects.serverobject.hackable.{GenericHackables, HackableBehavior} +import net.psforever.objects.serverobject.hackable.{GenericHackables, Hackable, HackableBehavior} import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior} import net.psforever.objects.serverobject.repair.{AmenityAutoRepair, RepairableAmenity, RepairableEntity} -import net.psforever.objects.serverobject.structures.{Building, PoweredAmenityControl} -import net.psforever.objects.serverobject.terminals.capture.CaptureTerminalAwareBehavior +import net.psforever.objects.serverobject.structures.{Amenity, Building, PoweredAmenityControl} +import net.psforever.objects.serverobject.terminals.Terminal +import net.psforever.objects.serverobject.terminals.capture.{CaptureTerminal, CaptureTerminalAwareBehavior} import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} import net.psforever.objects.vital.interaction.DamageResult +import net.psforever.objects.zones.Zone import net.psforever.objects.{GlobalDefinitions, Player, SimpleItem} +import net.psforever.packet.game.HackState1 +import net.psforever.services.local.{LocalAction, LocalServiceMessage} import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} +import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID} + +import scala.annotation.unused + +object ImplantTerminalMechControl { + private def FindPairedTerminalInterface( + zone: Zone, + mechGuid: PlanetSideGUID + ): Option[Amenity with Hackable] = { + zone + .map + .terminalToInterface + .find { case (guid, _) => guid == mechGuid.guid } + .flatMap { case (_, interfaceGuid) => zone.GUID(interfaceGuid) } + .collect { case terminal: Terminal if !terminal.Destroyed && terminal.HackedBy.isEmpty => terminal } + } +} /** - * An `Actor` that handles messages being dispatched to a specific `ImplantTerminalMech`. - * @param mech the "mech" object being governed - */ + * An `Actor` that handles messages being dispatched to a specific `ImplantTerminalMech`. + * @param mech the "mech" object being governed + */ class ImplantTerminalMechControl(mech: ImplantTerminalMech) - extends PoweredAmenityControl + extends PoweredAmenityControl with FactionAffinityBehavior.Check with MountableBehavior with HackableBehavior.GenericHackable @@ -41,9 +63,11 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech) .orElse(takesDamage) .orElse(canBeRepairedByNanoDispenser) .orElse(autoRepairBehavior) + .orElse(captureTerminalAwareBehaviour) def poweredStateLogic : Receive = commonBehavior + .orElse(hackableBehavior) .orElse(mountBehavior) .orElse { case CommonMessages.Use(player, Some(item: SimpleItem)) @@ -53,18 +77,19 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech) case b: Building if (b.Faction != player.Faction || b.CaptureTerminalIsHacked) && mech.HackedBy.isEmpty => sender() ! CommonMessages.Progress( GenericHackables.GetHackSpeed(player, mech), - GenericHackables.FinishHacking(mech, player, 3212836864L), - GenericHackables.HackingTickAction(progressType = 1, player, mech, item.GUID) + GenericHackables.FinishHacking(mech, player, hackValue = -1, hackClearValue = -1), + GenericHackables.HackingTickAction(HackState1.Unk1, player, mech, item.GUID) ) - case _ => ; + case _ => () } - case _ => ; + case _ => () } def unpoweredStateLogic: Receive = commonBehavior + .orElse(hackableBehavior) .orElse { - case _ => ; + case _ => () } override protected def mountTest( @@ -120,14 +145,11 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech) val zoneId = zone.id val events = zone.VehicleEvents mech.Seats.values.foreach(seat => - seat.occupant match { - case Some(player) => + seat.occupant.collect { + case player => seat.unmount(player) player.VehicleSeated = None - if (player.HasGUID) { - events ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(player.GUID, 4, unk2 = false, guid)) - } - case None => ; + events ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(player.GUID, 4, unk2=false, guid)) } ) } @@ -140,4 +162,123 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech) super.Restoration(obj) RepairableAmenity.RestorationOfHistory(obj) } + + override def performHack(player: Player, data: Option[Any], replyTo: ActorRef): Unit = { + //todo don't now how to properly hack this amenity + super.performHack(player, data, replyTo) + val zone = HackableObject.Zone + val guid = HackableObject.GUID + val localFaction = mech.Faction + val events = zone.LocalEvents + if (player.Faction == localFaction) { + if (mech.Owner.asInstanceOf[Building].CaptureTerminalIsHacked) { + //this is actually futile, as a hacked base does not grant access to the terminal + events ! LocalServiceMessage(localFaction.toString, LocalAction.SetEmpire(guid, localFaction)) + } + kickAllOccupantsNotOfFaction(zone, guid, mech, localFaction) + } else { + opposingFactionsMayAccess(zone, guid, localFaction) + kickAllOccupantsOfFaction(zone, guid, mech, localFaction) + } + ImplantTerminalMechControl + .FindPairedTerminalInterface(zone, guid) + .foreach(GenericHackables.FinishHacking(_, player, hackValue = -1, hackClearValue = -1)()) + } + + override def performClearHack(data: Option[Any], replyTo: ActorRef): Unit = { + //todo don't now how to properly unhack this amenity + HackableObject.HackedBy.collect { _ => + super.performClearHack(data, replyTo) + val toFaction = HackableObject.Faction + val zone = HackableObject.Zone + val guid = HackableObject.GUID + noAccessByOpposingFactions(zone, guid, toFaction) + kickAllOccupantsNotOfFaction(zone, guid, mech, toFaction) + } + } + + override protected def captureTerminalIsHacked(@unused terminal: CaptureTerminal): Unit = { + //todo don't now how to properly handle a hacked mech + super.captureTerminalIsHacked(terminal) + val zone = HackableObject.Zone + val guid = HackableObject.GUID + kickAllOccupantsNotOfFactionWithTest(zone, guid, mech, (_: PlanetSideEmpire.Value) => { true }) + } + + override protected def captureTerminalIsResecured(terminal: CaptureTerminal): Unit = { + //todo don't now how to properly handle a hacked mech + super.captureTerminalIsResecured(terminal) + //if hacked, correct + val zone = HackableObject.Zone + val guid = HackableObject.GUID + val toFaction = HackableObject.Faction + HackableObject.HackedBy.collect { + case hackInfo if hackInfo.hackerFaction != toFaction => + opposingFactionsMayAccess(zone, guid, toFaction) + } + } + + private def opposingFactionsMayAccess( + zone: Zone, + guid: PlanetSideGUID, + setToFaction: PlanetSideEmpire.Value + ): Unit = { + val events = zone.LocalEvents + opposingFactionsAre(setToFaction).foreach { faction => + events ! LocalServiceMessage(faction.toString, LocalAction.SetEmpire(guid, faction)) + } + } + + private def noAccessByOpposingFactions( + zone: Zone, + guid: PlanetSideGUID, + setToFaction: PlanetSideEmpire.Value + ): Unit = { + val events = zone.LocalEvents + opposingFactionsAre(setToFaction).foreach { faction => + events ! LocalServiceMessage(faction.toString, LocalAction.SetEmpire(guid, setToFaction)) + } + } + + private def opposingFactionsAre(faction: PlanetSideEmpire.Value): PlanetSideEmpire.ValueSet = { + PlanetSideEmpire + .values + .filterNot { f => f == PlanetSideEmpire.NEUTRAL && f == faction } + } + + private def kickAllOccupantsOfFaction( + zone: Zone, + guid: PlanetSideGUID, + obj: Mountable, + isFaction: PlanetSideEmpire.Value + ): Unit = { + kickAllOccupantsNotOfFactionWithTest(zone, guid, obj, (a: PlanetSideEmpire.Value) => { a == isFaction }) + } + + private def kickAllOccupantsNotOfFaction( + zone: Zone, + guid: PlanetSideGUID, + obj: Mountable, + isFaction: PlanetSideEmpire.Value + ): Unit = { + kickAllOccupantsNotOfFactionWithTest(zone, guid, obj, (a: PlanetSideEmpire.Value) => { a != isFaction }) + } + + private def kickAllOccupantsNotOfFactionWithTest( + zone: Zone, + guid: PlanetSideGUID, + obj: Mountable, + test: PlanetSideEmpire.Value => Boolean + ): Unit = { + val zoneId = zone.id + val events = zone.LocalEvents + obj.Seats.values.foreach(seat => + seat.occupant.collect { + case player if test(player.Faction) => + seat.unmount(player) + player.VehicleSeated = None + events ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(player.GUID, 4, unk2 = false, guid)) + } + ) + } } diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala index 09e0973f..12be6526 100644 --- a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala @@ -13,7 +13,7 @@ import net.psforever.objects.serverobject.terminals.capture.{CaptureTerminal, Ca import net.psforever.objects.serverobject.turret.auto.AutomatedTurret.Target import net.psforever.objects.serverobject.turret.auto.{AffectedByAutomaticTurretFire, AutomatedTurret, AutomatedTurretBehavior} import net.psforever.objects.vital.interaction.DamageResult -import net.psforever.packet.game.ChangeFireModeMessage +import net.psforever.packet.game.{ChangeFireModeMessage, HackState1} import net.psforever.services.Service import net.psforever.services.vehicle.support.TurretUpgrader import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} @@ -67,7 +67,7 @@ class FacilityTurretControl(turret: FacilityTurret) sender() ! CommonMessages.Progress( 1.25f, WeaponTurrets.FinishUpgradingMannedTurret(TurretObject, player, item, upgrade), - WeaponTurrets.TurretUpgradingTickAction(progressType = 2, player, TurretObject, item.GUID) + WeaponTurrets.TurretUpgradingTickAction(HackState1.Unk2, player, TurretObject, item.GUID) ) } case TurretUpgrader.UpgradeCompleted(_) => @@ -330,6 +330,21 @@ class FacilityTurretControl(turret: FacilityTurret) } override protected def captureTerminalIsHacked(terminal: CaptureTerminal): Unit = { + super.captureTerminalIsHacked(terminal) + // Remove seated occupants + val guid = turret.GUID + val zone = turret.Zone + val zoneId = zone.id + val events = zone.VehicleEvents + turret.Seats.values.zipWithIndex.foreach { + case (seat, seat_num) => + seat.occupant.collect { + case player => + seat.unmount(player) + player.VehicleSeated = None + events ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(player.GUID, seat_num, unk2=true, guid)) + } + } captureTerminalChanges(terminal, super.captureTerminalIsHacked, actionDelays = 3000L) } diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/WeaponTurrets.scala b/src/main/scala/net/psforever/objects/serverobject/turret/WeaponTurrets.scala index ae839f0e..9dd64c31 100644 --- a/src/main/scala/net/psforever/objects/serverobject/turret/WeaponTurrets.scala +++ b/src/main/scala/net/psforever/objects/serverobject/turret/WeaponTurrets.scala @@ -5,7 +5,7 @@ import net.psforever.objects.avatar.Certification import net.psforever.objects.ce.Deployable import net.psforever.objects.serverobject.hackable.GenericHackables.updateTurretUpgradeTime import net.psforever.objects.{Player, Tool, TurretDeployable} -import net.psforever.packet.game.{HackMessage, HackState, InventoryStateMessage} +import net.psforever.packet.game.{HackMessage, HackState, HackState1, HackState7, InventoryStateMessage} import net.psforever.services.Service import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.local.{LocalAction, LocalServiceMessage} @@ -73,26 +73,27 @@ object WeaponTurrets { * @return `true`, if the next cycle of progress should occur; * `false`, otherwise */ - def TurretUpgradingTickAction(progressType: Int, tplayer: Player, turret: FacilityTurret, tool_guid: PlanetSideGUID)( + def TurretUpgradingTickAction(progressType: HackState1, tplayer: Player, turret: FacilityTurret, tool_guid: PlanetSideGUID)( progress: Float ): Boolean = { - //hack state for progress bar visibility - val vis = if (progress <= 0L) { - HackState.Start + val (progressState, progressGrade) = if (progress <= 0L) { + (HackState.Start, 0) } else if (progress >= 100L) { - HackState.Finished + (HackState.Finished, 100) + } else if (turret.Destroyed) { + (HackState.Cancelled, 0) } else { updateTurretUpgradeTime() - HackState.Ongoing + (HackState.Ongoing, progress.toInt) } turret.Zone.AvatarEvents ! AvatarServiceMessage( tplayer.Name, AvatarAction.SendResponse( Service.defaultPlayerGUID, - HackMessage(progressType, turret.GUID, tplayer.GUID, progress.toInt, 0L, vis, 8L) + HackMessage(progressType, turret.GUID, tplayer.GUID, progressGrade, -1f, progressState, HackState7.Unk8) ) ) - vis != HackState.Cancelled + progressState != HackState.Cancelled } def FinishHackingTurretDeployable(target: TurretDeployable, hacker: Player)(): Unit = { diff --git a/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala b/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala index 0f9687bc..8a5d48a2 100644 --- a/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala +++ b/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala @@ -179,8 +179,8 @@ class VehicleControl(vehicle: Vehicle) if (vehicle.Faction != player.Faction) { sender() ! CommonMessages.Progress( GenericHackables.GetHackSpeed(player, vehicle), - Vehicles.FinishHackingVehicle(vehicle, player, 3212836864L), - GenericHackables.HackingTickAction(progressType = 1, player, vehicle, item.GUID) + Vehicles.FinishHackingVehicle(vehicle, player), + GenericHackables.HackingTickAction(HackState1.Unk1, player, vehicle, item.GUID) ) } diff --git a/src/main/scala/net/psforever/packet/game/HackMessage.scala b/src/main/scala/net/psforever/packet/game/HackMessage.scala index 77df26d8..1df1afb5 100644 --- a/src/main/scala/net/psforever/packet/game/HackMessage.scala +++ b/src/main/scala/net/psforever/packet/game/HackMessage.scala @@ -1,10 +1,52 @@ // Copyright (c) 2017 PSForever package net.psforever.packet.game +import enumeratum.values.{IntEnum, IntEnumEntry} +import net.psforever.packet.GamePacketOpcode.Type import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} import net.psforever.types.PlanetSideGUID -import scodec.Codec +import scodec.bits.BitVector +import scodec.{Attempt, Codec} import scodec.codecs._ +import shapeless.{::, HNil} + +sealed abstract class HackState1(val value: Int) extends IntEnumEntry + +object HackState1 extends IntEnum[HackState1] { + val values: IndexedSeq[HackState1] = findValues + + case object Unk0 extends HackState1(value = 0) + case object Unk1 extends HackState1(value = 1) + case object Unk2 extends HackState1(value = 2) + case object Unk3 extends HackState1(value = 3) + + implicit val codec: Codec[HackState1] = PacketHelpers.createIntEnumCodec(this, uint2) +} + +sealed abstract class HackState7(val value: Int) extends IntEnumEntry + +object HackState7 extends IntEnum[HackState7] { + val values: IndexedSeq[HackState7] = findValues + + case object Unk0 extends HackState7(value = 0) + case object Unk1 extends HackState7(value = 1) + case object Unk2 extends HackState7(value = 2) + case object Unk3 extends HackState7(value = 3) + case object Unk4 extends HackState7(value = 4) + case object Unk5 extends HackState7(value = 5) + case object Unk6 extends HackState7(value = 6) + case object Unk7 extends HackState7(value = 7) + case object Unk8 extends HackState7(value = 8) + + implicit val codec: Codec[HackState7] = (PacketHelpers.createIntEnumCodec(this, uint8) :: ignore(size = 24)).xmap[HackState7]( + { + case a :: _ :: HNil => a + }, + { + a => a :: () :: HNil + } + ) +} /** * An `Enumeration` of the various states and activities of the hacking process. @@ -16,12 +58,21 @@ import scodec.codecs._ * `Hacked` modifies the target of the hack.
* `HackCleared` modifies the target of the hack, opposite of `Hacked`. */ -object HackState extends Enumeration { - type Type = Value +sealed abstract class HackState(val value: Int) extends IntEnumEntry - val Unknown0, Start, Cancelled, Ongoing, Finished, Unknown5, Hacked, HackCleared = Value +object HackState extends IntEnum[HackState] { + val values: IndexedSeq[HackState] = findValues - implicit val codec = PacketHelpers.createEnumerationCodec(this, uint8L) + case object Unknown0 extends HackState(value = 0) + case object Start extends HackState(value = 1) + case object Cancelled extends HackState(value = 2) + case object Ongoing extends HackState(value = 3) + case object Finished extends HackState(value = 4) + case object Unknown5 extends HackState(value = 5) + case object Hacked extends HackState(value = 6) + case object HackCleared extends HackState(value = 7) + + implicit val codec: Codec[HackState] = PacketHelpers.createIntEnumCodec(this, uint8L) } /** @@ -44,11 +95,12 @@ object HackState extends Enumeration { * As mentioned, one of the unexpected uses of this message * will assist the conversion of allied facility turreted weapons to their upgraded armaments. * @param unk1 na; - * 0 commonly; - * 2 when performing (phalanx) upgrades; - * 3 for building objects during login phase; - * hack type? - * possibly player hacking level 0-3? + * 0 commonly; + * 1 unknown; + * 2 when performing (phalanx) upgrades; + * 3 for building objects during login phase; + * hack type? + * possibly player hacking level 0-3? * @param target_guid the target of the hack * @param player_guid the player * @param progress the amount of progress visible; @@ -58,31 +110,44 @@ object HackState extends Enumeration { * doesn't seem to be `char_id`? * @param hack_state hack state * @param unk7 na; + * usually 8; + * values 3-7 noted for the Hacked state; * 5 - boost pain field at matrixing terminal? - * usually, 8? */ final case class HackMessage( - unk1: Int, - target_guid: PlanetSideGUID, - player_guid: PlanetSideGUID, - progress: Int, - unk5: Long, - hack_state: HackState.Value, - unk7: Long -) extends PlanetSideGamePacket { + unk1: HackState1, + target_guid: PlanetSideGUID, + player_guid: PlanetSideGUID, + progress: Int, + unk5: Float, + hack_state: HackState, + unk7: HackState7 + ) extends PlanetSideGamePacket { type Packet = HackMessage - def opcode = GamePacketOpcode.HackMessage - def encode = HackMessage.encode(this) + def opcode: Type = GamePacketOpcode.HackMessage + def encode: Attempt[BitVector] = HackMessage.encode(this) } object HackMessage extends Marshallable[HackMessage] { + def apply( + unk1: HackState1, + target_guid: PlanetSideGUID, + player_guid: PlanetSideGUID, + progress: Int, + unk5: Int, + hack_state: HackState, + unk7: HackState7 + ): HackMessage = { + new HackMessage(unk1, target_guid, player_guid, progress, unk5.toFloat, hack_state, unk7) + } + implicit val codec: Codec[HackMessage] = ( - ("unk1" | uint2L) :: + ("unk1" | HackState1.codec) :: ("object_guid" | PlanetSideGUID.codec) :: ("player_guid" | PlanetSideGUID.codec) :: ("progress" | uint8L) :: - ("unk5" | uint32L) :: + ("unk5" | floatL) :: ("hack_state" | HackState.codec) :: - ("unk7" | uint32L) + ("unk7" | HackState7.codec) ).as[HackMessage] } diff --git a/src/main/scala/net/psforever/services/local/LocalService.scala b/src/main/scala/net/psforever/services/local/LocalService.scala index e1e18d51..c8cbea04 100644 --- a/src/main/scala/net/psforever/services/local/LocalService.scala +++ b/src/main/scala/net/psforever/services/local/LocalService.scala @@ -96,10 +96,10 @@ class LocalService(zone: Zone) extends Actor { LocalResponse.SendHackMessageHackCleared(target.GUID, unk1, unk2) ) ) - case LocalAction.HackTemporarily(player_guid, _, target, unk1, duration, unk2) => - hackClearer ! HackClearActor.ObjectIsHacked(target, zone, unk1, unk2, duration) + case LocalAction.HackTemporarily(player_guid, _, target, hackValue, hackClear, duration, unk2) => + hackClearer ! HackClearActor.ObjectIsHacked(target, zone, hackClear, unk2, duration) LocalEvents.publish( - LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.HackObject(target.GUID, unk1, unk2)) + LocalServiceResponse(s"/$forChannel/Local", player_guid, LocalResponse.HackObject(target.GUID, hackValue, unk2)) ) case LocalAction.ClearTemporaryHack(_, target) => hackClearer ! HackClearActor.ObjectIsResecured(target) diff --git a/src/main/scala/net/psforever/services/local/LocalServiceMessage.scala b/src/main/scala/net/psforever/services/local/LocalServiceMessage.scala index 6a162058..df0fdb5a 100644 --- a/src/main/scala/net/psforever/services/local/LocalServiceMessage.scala +++ b/src/main/scala/net/psforever/services/local/LocalServiceMessage.scala @@ -14,7 +14,7 @@ import net.psforever.objects.{PlanetSideGameObject, TelepadDeployable, Vehicle} import net.psforever.packet.PlanetSideGamePacket import net.psforever.packet.game.GenericObjectActionEnum.GenericObjectActionEnum import net.psforever.packet.game.PlanetsideAttributeEnum.PlanetsideAttributeEnum -import net.psforever.packet.game.{ChatMsg, DeployableInfo, DeploymentAction, GenericAction, TriggeredSound} +import net.psforever.packet.game.{ChatMsg, DeployableInfo, DeploymentAction, GenericAction, HackState7, TriggeredSound} import net.psforever.services.hart.HartTimer.OrbitalShuttleEvent import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3} @@ -43,15 +43,16 @@ object LocalAction { pos: Vector3, deletionEffect: Int ) extends Action - final case class HackClear(player_guid: PlanetSideGUID, target: PlanetSideServerObject, unk1: Long, unk2: Long = 8L) + final case class HackClear(player_guid: PlanetSideGUID, target: PlanetSideServerObject, unk1: Long, unk2: HackState7 = HackState7.Unk8) extends Action final case class HackTemporarily( player_guid: PlanetSideGUID, continent: Zone, target: PlanetSideServerObject, - unk1: Long, + hackValue: Long, + hackClearValue: Long, duration: Int, - unk2: Long = 8L + unk2: HackState7 = HackState7.Unk8 ) extends Action final case class ClearTemporaryHack(player_guid: PlanetSideGUID, target: PlanetSideServerObject with Hackable) extends Action diff --git a/src/main/scala/net/psforever/services/local/LocalServiceResponse.scala b/src/main/scala/net/psforever/services/local/LocalServiceResponse.scala index 277dddcc..ffb1bb3f 100644 --- a/src/main/scala/net/psforever/services/local/LocalServiceResponse.scala +++ b/src/main/scala/net/psforever/services/local/LocalServiceResponse.scala @@ -34,8 +34,8 @@ object LocalResponse { pos: Vector3, deletionEffect: Int ) extends Response - final case class SendHackMessageHackCleared(target_guid: PlanetSideGUID, unk1: Long, unk2: Long) extends Response - final case class HackObject(target_guid: PlanetSideGUID, unk1: Long, unk2: Long) extends Response + final case class SendHackMessageHackCleared(target_guid: PlanetSideGUID, unk1: Long, unk2: HackState7) extends Response + final case class HackObject(target_guid: PlanetSideGUID, unk1: Long, unk2: HackState7) extends Response final case class SendPacket(packet: PlanetSideGamePacket) extends Response final case class PlanetsideAttribute(target_guid: PlanetSideGUID, attribute_number: PlanetsideAttributeEnum, attribute_value: Long) diff --git a/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala b/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala index 689d5e23..b9a4f470 100644 --- a/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala +++ b/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala @@ -10,7 +10,7 @@ import net.psforever.objects.serverobject.structures.Building import net.psforever.objects.serverobject.terminals.capture.CaptureTerminal import net.psforever.objects.zones.Zone import net.psforever.objects.Default -import net.psforever.packet.game.{GenericAction, PlanetsideAttributeEnum} +import net.psforever.packet.game.{GenericAction, HackState7, PlanetsideAttributeEnum} import net.psforever.objects.sourcing.PlayerSource import net.psforever.services.Service import net.psforever.services.local.support.HackCaptureActor.GetHackingFaction @@ -231,7 +231,7 @@ class HackCaptureActor extends Actor { } NotifyHackStateChange(terminal, isResecured = true) // todo: this appears to be the way to reset the base warning lights after the hack finishes but it doesn't seem to work. - context.parent ! HackClearActor.SendHackMessageHackCleared(building.GUID, terminal.Zone.id, 3212836864L, 8L) //call up + context.parent ! HackClearActor.SendHackMessageHackCleared(building.GUID, terminal.Zone.id, 3212836864L, HackState7.Unk8) //call up } private def RestartTimer(): Unit = { diff --git a/src/main/scala/net/psforever/services/local/support/HackClearActor.scala b/src/main/scala/net/psforever/services/local/support/HackClearActor.scala index c6f8e8bb..6cb3e5bc 100644 --- a/src/main/scala/net/psforever/services/local/support/HackClearActor.scala +++ b/src/main/scala/net/psforever/services/local/support/HackClearActor.scala @@ -2,12 +2,12 @@ package net.psforever.services.local.support import java.util.concurrent.TimeUnit - import akka.actor.{Actor, Cancellable} import net.psforever.objects.Default import net.psforever.objects.serverobject.hackable.Hackable import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} import net.psforever.objects.zones.Zone +import net.psforever.packet.game.HackState7 import net.psforever.types.PlanetSideGUID import scala.annotation.tailrec @@ -156,7 +156,7 @@ object HackClearActor { target: PlanetSideServerObject, zone: Zone, unk1: Long, - unk2: Long, + unk2: HackState7, duration: Int, time: Long = System.currentTimeMillis() ) @@ -172,7 +172,7 @@ object HackClearActor { * @param obj the server object * @param zone_id the zone in which the object resides */ - final case class SendHackMessageHackCleared(obj: PlanetSideGUID, zone_id: String, unk1: Long, unk2: Long) + final case class SendHackMessageHackCleared(obj: PlanetSideGUID, zone_id: String, unk1: Long, unk2: HackState7) /** * Internal message used to signal a test of the queued door information. @@ -192,7 +192,7 @@ object HackClearActor { target: PlanetSideServerObject, zone: Zone, unk1: Long, - unk2: Long, + unk2: HackState7, time: Long, duration: Long ) diff --git a/src/main/scala/net/psforever/zones/Zones.scala b/src/main/scala/net/psforever/zones/Zones.scala index ba5db7ae..6f969468 100644 --- a/src/main/scala/net/psforever/zones/Zones.scala +++ b/src/main/scala/net/psforever/zones/Zones.scala @@ -21,7 +21,7 @@ import net.psforever.objects.serverobject.resourcesilo.ResourceSilo import net.psforever.objects.serverobject.shuttle.OrbitalShuttlePad import net.psforever.objects.serverobject.structures.{Building, BuildingDefinition, FoundationBuilder, StructureType, WarpGate} import net.psforever.objects.serverobject.terminals.capture.{CaptureTerminal, CaptureTerminalDefinition} -import net.psforever.objects.serverobject.terminals.implant.ImplantTerminalMech +import net.psforever.objects.serverobject.terminals.implant.{ImplantTerminalInterface, ImplantTerminalMech} import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects.serverobject.turret.{FacilityTurret, FacilityTurretDefinition, VanuSentry} import net.psforever.objects.serverobject.zipline.ZipLinePath @@ -637,7 +637,7 @@ object Zones { zoneMap.addLocalObject( closestTerminal.guid, - Terminal.Constructor(closestTerminal.position, GlobalDefinitions.implant_terminal_interface), + ImplantTerminalInterface.Constructor(closestTerminal.position, GlobalDefinitions.implant_terminal_interface), owningBuildingGuid = ownerGuid ) diff --git a/src/test/scala/game/HackMessageTest.scala b/src/test/scala/game/HackMessageTest.scala index bd6bc943..dd6c77a9 100644 --- a/src/test/scala/game/HackMessageTest.scala +++ b/src/test/scala/game/HackMessageTest.scala @@ -14,20 +14,20 @@ class HackMessageTest extends Specification { "decode" in { PacketCoding.decodePacket(string).require match { case HackMessage(unk1, target_guid, player_guid, progress, unk5, hack_state, unk7) => - unk1 mustEqual 0 + unk1 mustEqual HackState1.Unk0 target_guid mustEqual PlanetSideGUID(1024) player_guid mustEqual PlanetSideGUID(3607) progress mustEqual 0 - unk5 mustEqual 3212836864L + unk5 mustEqual -1.0f hack_state mustEqual HackState.Start - unk7 mustEqual 8L + unk7 mustEqual HackState7.Unk8 case _ => ko } } "encode" in { - val msg = HackMessage(0, PlanetSideGUID(1024), PlanetSideGUID(3607), 0, 3212836864L, HackState.Start, 8L) + val msg = HackMessage(HackState1.Unk0, PlanetSideGUID(1024), PlanetSideGUID(3607), 0, -1.0f, HackState.Start, HackState7.Unk8) val pkt = PacketCoding.encodePacket(msg).require.toByteVector pkt mustEqual string } diff --git a/src/test/scala/service/LocalServiceTest.scala b/src/test/scala/service/LocalServiceTest.scala index 7c96d6ed..05b8fe7b 100644 --- a/src/test/scala/service/LocalServiceTest.scala +++ b/src/test/scala/service/LocalServiceTest.scala @@ -136,9 +136,9 @@ class HackClearTest extends ActorTest { "pass HackClear" in { val service = system.actorOf(Props(classOf[LocalService], Zone.Nowhere), "l_service") service ! Service.Join("test") - service ! LocalServiceMessage("test", LocalAction.HackClear(PlanetSideGUID(10), obj, 0L, 1000L)) + service ! LocalServiceMessage("test", LocalAction.HackClear(PlanetSideGUID(10), obj, 0L, HackState7.Unk8)) expectMsg( - LocalServiceResponse("/test/Local", PlanetSideGUID(10), LocalResponse.SendHackMessageHackCleared(PlanetSideGUID(40), 0L, 1000L)) + LocalServiceResponse("/test/Local", PlanetSideGUID(10), LocalResponse.SendHackMessageHackCleared(PlanetSideGUID(40), 0L, HackState7.Unk8)) ) } }