diff --git a/common/src/main/scala/net/psforever/objects/serverobject/hackable/Hackable.scala b/common/src/main/scala/net/psforever/objects/serverobject/hackable/Hackable.scala new file mode 100644 index 00000000..ef5bbd3f --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/hackable/Hackable.scala @@ -0,0 +1,49 @@ +package net.psforever.objects.serverobject.hackable + +import net.psforever.objects.Player +import net.psforever.packet.game.{PlanetSideGUID, TriggeredSound} +import net.psforever.types.Vector3 + +trait Hackable { + /** An entry that maintains a reference to the `Player`, and the player's GUID and location when the message was received. */ + private var hackedBy : Option[(Player, PlanetSideGUID, Vector3)] = None + + private var hackSound : TriggeredSound.Value = TriggeredSound.HackDoor + + def HackedBy : Option[(Player, PlanetSideGUID, Vector3)] = hackedBy + + def HackedBy_=(agent : Player) : Option[(Player, PlanetSideGUID, Vector3)] = HackedBy_=(Some(agent)) + + /** + * Set the hack state of this object by recording important information about the player that caused it. + * Set the hack state if there is no current hack state. + * Override the hack state with a new hack state if the new user has different faction affiliation. + * @param agent a `Player`, or no player + * @return the player hack entry + */ + def HackedBy_=(agent : Option[Player]) : Option[(Player, PlanetSideGUID, Vector3)] = { + hackedBy match { + case None => + //set the hack state if there is no current hack state + if(agent.isDefined) { + hackedBy = Some(agent.get, agent.get.GUID, agent.get.Position) + } + case Some(_) => + //clear the hack state + if(agent.isEmpty) { + hackedBy = None + } + //override the hack state with a new hack state if the new user has different faction affiliation + else if(agent.get.Faction != hackedBy.get._1.Faction) { + hackedBy = Some(agent.get, agent.get.GUID, agent.get.Position) + } + } + HackedBy + } + + def HackSound : TriggeredSound.Value = hackSound + def HackSound_=(sound : TriggeredSound.Value) : TriggeredSound.Value = { + hackSound = sound + hackSound + } +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLock.scala b/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLock.scala index 8142fd9a..6c21bdc4 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLock.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLock.scala @@ -1,10 +1,9 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.locks -import net.psforever.objects.Player +import net.psforever.objects.serverobject.hackable.Hackable import net.psforever.objects.serverobject.structures.Amenity -import net.psforever.packet.game.PlanetSideGUID -import net.psforever.types.Vector3 +import net.psforever.packet.game.TriggeredSound /** * A structure-owned server object that is a "door lock."
@@ -15,44 +14,9 @@ import net.psforever.types.Vector3 * The `IFFLock` is ideally associated with a server map object - a `Door` - to which it acts as a gatekeeper. * @param idef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields */ -class IFFLock(private val idef : IFFLockDefinition) extends Amenity { - /** - * An entry that maintains a reference to the `Player`, and the player's GUID and location when the message was received. - */ - private var hackedBy : Option[(Player, PlanetSideGUID, Vector3)] = None - - def HackedBy : Option[(Player, PlanetSideGUID, Vector3)] = hackedBy - - def HackedBy_=(agent : Player) : Option[(Player, PlanetSideGUID, Vector3)] = HackedBy_=(Some(agent)) - - /** - * Set the hack state of this object by recording important information about the player that caused it. - * Set the hack state if there is no current hack state. - * Override the hack state with a new hack state if the new user has different faction affiliation. - * @param agent a `Player`, or no player - * @return the player hack entry - */ - def HackedBy_=(agent : Option[Player]) : Option[(Player, PlanetSideGUID, Vector3)] = { - hackedBy match { - case None => - //set the hack state if there is no current hack state - if(agent.isDefined) { - hackedBy = Some(agent.get, agent.get.GUID, agent.get.Position) - } - case Some(_) => - //clear the hack state - if(agent.isEmpty) { - hackedBy = None - } - //override the hack state with a new hack state if the new user has different faction affiliation - else if(agent.get.Faction != hackedBy.get._1.Faction) { - hackedBy = Some(agent.get, agent.get.GUID, agent.get.Position) - } - } - HackedBy - } - +class IFFLock(private val idef : IFFLockDefinition) extends Amenity with Hackable { def Definition : IFFLockDefinition = idef + HackSound = TriggeredSound.HackDoor } object IFFLock { diff --git a/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockControl.scala index 0af05811..960a1c3c 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockControl.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockControl.scala @@ -16,7 +16,7 @@ class IFFLockControl(lock : IFFLock) extends Actor with FactionAffinityBehavior. def receive : Receive = checkBehavior.orElse { case CommonMessages.Hack(player) => lock.HackedBy = player - + sender ! true case CommonMessages.ClearHack() => lock.HackedBy = None diff --git a/common/src/main/scala/net/psforever/objects/serverobject/mblocker/Locker.scala b/common/src/main/scala/net/psforever/objects/serverobject/mblocker/Locker.scala index d614b53a..c91c787d 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/mblocker/Locker.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/mblocker/Locker.scala @@ -3,10 +3,13 @@ package net.psforever.objects.serverobject.mblocker import akka.actor.{ActorContext, Props} import net.psforever.objects.GlobalDefinitions +import net.psforever.objects.serverobject.hackable.Hackable import net.psforever.objects.serverobject.structures.Amenity +import net.psforever.packet.game.TriggeredSound -class Locker extends Amenity { +class Locker extends Amenity with Hackable { def Definition : LockerDefinition = GlobalDefinitions.mb_locker + HackSound = TriggeredSound.HackTerminal } object Locker { diff --git a/common/src/main/scala/net/psforever/objects/serverobject/mblocker/LockerControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/mblocker/LockerControl.scala index 2e037e64..7bced8d8 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/mblocker/LockerControl.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/mblocker/LockerControl.scala @@ -2,6 +2,7 @@ package net.psforever.objects.serverobject.mblocker import akka.actor.Actor +import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} /** @@ -12,6 +13,11 @@ class LockerControl(locker : Locker) extends Actor with FactionAffinityBehavior. def FactionObject : FactionAffinity = locker def receive : Receive = checkBehavior.orElse { + case CommonMessages.Hack(player) => + locker.HackedBy = player + sender ! true + case CommonMessages.ClearHack() => + locker.HackedBy = None case _ => ; } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala index 753cdb65..fcc8c073 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala @@ -2,6 +2,7 @@ package net.psforever.objects.serverobject.terminals import akka.actor.Actor +import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} /** @@ -18,6 +19,11 @@ class ProximityTerminalControl(term : Terminal with ProximityUnit) extends Actor def receive : Receive = checkBehavior .orElse(proximityBehavior) .orElse { + case CommonMessages.Hack(player) => + term.HackedBy = player + sender ! true + case CommonMessages.ClearHack() => + term.HackedBy = None case _ => ; } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala index 7a8676bb..61f587e7 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala @@ -3,48 +3,17 @@ package net.psforever.objects.serverobject.terminals import net.psforever.objects.Player import net.psforever.objects.definition.VehicleDefinition +import net.psforever.objects.serverobject.hackable.Hackable import net.psforever.objects.serverobject.structures.Amenity -import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} -import net.psforever.types.{TransactionType, Vector3} +import net.psforever.packet.game.{ItemTransactionMessage, TriggeredSound} +import net.psforever.types.TransactionType /** * A structure-owned server object that is a "terminal" that can be accessed for amenities and services. * @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields */ -class Terminal(tdef : TerminalDefinition) extends Amenity { - /** An entry that maintains a reference to the `Player`, and the player's GUID and location when the message was received. */ - private var hackedBy : Option[(Player, PlanetSideGUID, Vector3)] = None - - def HackedBy : Option[(Player, PlanetSideGUID, Vector3)] = hackedBy - - def HackedBy_=(agent : Player) : Option[(Player, PlanetSideGUID, Vector3)] = HackedBy_=(Some(agent)) - - /** - * Set the hack state of this object by recording important information about the player that caused it. - * Set the hack state if there is no current hack state. - * Override the hack state with a new hack state if the new user has different faction affiliation. - * @param agent a `Player`, or no player - * @return the player hack entry - */ - def HackedBy_=(agent : Option[Player]) : Option[(Player, PlanetSideGUID, Vector3)] = { - hackedBy match { - case None => - //set the hack state if there is no current hack state - if(agent.isDefined) { - hackedBy = Some(agent.get, agent.get.GUID, agent.get.Position) - } - case Some(_) => - //clear the hack state - if(agent.isEmpty) { - hackedBy = None - } - //override the hack state with a new hack state if the new user has different faction affiliation - else if(agent.get.Faction != hackedBy.get._1.Faction) { - hackedBy = Some(agent.get, agent.get.GUID, agent.get.Position) - } - } - HackedBy - } +class Terminal(tdef : TerminalDefinition) extends Amenity with Hackable { + HackSound = TriggeredSound.HackTerminal //the following fields and related methods are neither finalized nor integrated; GOTO Request private var health : Int = 100 //TODO not real health value diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala index 4efa324e..7474706c 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala @@ -2,6 +2,7 @@ package net.psforever.objects.serverobject.terminals import akka.actor.Actor +import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} /** @@ -15,6 +16,12 @@ class TerminalControl(term : Terminal) extends Actor with FactionAffinityBehavio case Terminal.Request(player, msg) => sender ! Terminal.TerminalMessage(player, msg, term.Request(player, msg)) + case CommonMessages.Hack(player) => + term.HackedBy = player + sender ! true + case CommonMessages.ClearHack() => + term.HackedBy = None + case _ => ; } diff --git a/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala b/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala index 007cbbc9..3e78aba3 100644 --- a/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala @@ -118,6 +118,7 @@ import scodec.codecs._ * `77 - Cavern Facility Captures. Value is the number of captures`
* `78 - Cavern Kills. Value is the number of kills`
* `106 - Custom Head`
+ * `116 - Apply colour to REK beam and REK icon above players (0 = yellow, 1 = red, 2 = purple, 3 = blue)`
* Client to Server :
* `106 - Custom Head`
*
diff --git a/common/src/main/scala/net/psforever/packet/game/TriggerSoundMessage.scala b/common/src/main/scala/net/psforever/packet/game/TriggerSoundMessage.scala index 24cc5fa9..68bda057 100644 --- a/common/src/main/scala/net/psforever/packet/game/TriggerSoundMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/TriggerSoundMessage.scala @@ -16,8 +16,8 @@ object TriggeredSound extends Enumeration { val SpawnInTube, - Unknown1, - Hack, + HackTerminal, + HackVehicle, HackDoor, Unknown4, LockedOut, diff --git a/common/src/main/scala/net/psforever/packet/game/UseItemMessage.scala b/common/src/main/scala/net/psforever/packet/game/UseItemMessage.scala index 0df5c38f..b66180b9 100644 --- a/common/src/main/scala/net/psforever/packet/game/UseItemMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/UseItemMessage.scala @@ -9,7 +9,7 @@ import scodec.codecs._ /** * (Where the child object was before it was moved is not specified or important.)
* @param avatar_guid the player. - * @param unk1 dont know how call that. It's the "item" ID when use a rek to hack or a medkit to heal. + * @param item_used_guid The "item" GUID used e.g. a rek to hack or a medkit to heal. * @param object_guid can be : Door, Terminal, Avatar (medkit). * @param unk2 ??? * @param unk3 ??? true when use a rek (false when door, medkit or open equip term) @@ -21,7 +21,7 @@ import scodec.codecs._ * @param itemType object ID from game_objects.adb (ex 612 is an equipment terminal, for medkit we have 121 (avatar)) */ final case class UseItemMessage(avatar_guid : PlanetSideGUID, - unk1 : Int, + item_used_guid : Int, object_guid : PlanetSideGUID, unk2 : Long, unk3 : Boolean, @@ -40,7 +40,7 @@ final case class UseItemMessage(avatar_guid : PlanetSideGUID, object UseItemMessage extends Marshallable[UseItemMessage] { implicit val codec : Codec[UseItemMessage] = ( ("avatar_guid" | PlanetSideGUID.codec) :: - ("unk1" | uint16L) :: + ("item_used_guid" | uint16L) :: ("object_guid" | PlanetSideGUID.codec) :: ("unk2" | uint32L) :: ("unk3" | bool) :: diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 6b2bbad3..714ae193 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -24,6 +24,7 @@ import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.deploy.Deployment import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} import net.psforever.objects.serverobject.doors.Door +import net.psforever.objects.serverobject.hackable.Hackable import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech import net.psforever.objects.serverobject.locks.IFFLock import net.psforever.objects.serverobject.mblocker.Locker @@ -44,8 +45,10 @@ import services.vehicle.VehicleAction.UnstowEquipment import services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse} import scala.annotation.tailrec +import scala.concurrent.Future import scala.concurrent.duration._ import scala.util.Success +import akka.pattern.ask class WorldSessionActor extends Actor with MDCContextAware { import WorldSessionActor._ @@ -391,13 +394,23 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(GenericObjectStateMsg(door_guid, 17)) case LocalResponse.HackClear(target_guid, unk1, unk2) => + // Reset hack state for all players sendResponse(HackMessage(0, target_guid, guid, 0, unk1, HackState.HackCleared, unk2)) + // Set the object faction displayed back to it's original owner faction + sendResponse(SetEmpireMessage(target_guid, continent.GUID(target_guid).get.asInstanceOf[FactionAffinity].Faction)) case LocalResponse.HackObject(target_guid, unk1, unk2) => - if(tplayer_guid != guid) { + if(tplayer_guid != guid && continent.GUID(target_guid).get.asInstanceOf[Hackable].HackedBy.get._1.Faction != player.Faction) { + // If the player is not in the faction that hacked this object then send the packet that it's been hacked, so they can either unhack it or use the hacked object + // Don't send this to the faction that hacked the object, otherwise it will interfere with the new SetEmpireMessage QoL change that changes the object colour to their faction (but only visible to that faction) sendResponse(HackMessage(0, target_guid, guid, 100, unk1, HackState.Hacked, unk2)) } + if(continent.GUID(target_guid).get.asInstanceOf[Hackable].HackedBy.get._1.Faction == player.Faction){ + // Make the hacked object look like it belongs to the hacking empire, but only for that empire's players (so that infiltrators on stealth missions won't be given away to opposing factions) + sendResponse(SetEmpireMessage(target_guid, player.Faction)) + } + case LocalResponse.ProximityTerminalEffect(object_guid, effectState) => if(tplayer_guid != guid) { sendResponse(ProximityTerminalUseMessage(PlanetSideGUID(0), object_guid, effectState)) @@ -1510,7 +1523,7 @@ class WorldSessionActor extends Actor with MDCContextAware { if(progressBarVal > 100) { //done progressBarValue = None log.info(s"Hacked a $target") - sendResponse(HackMessage(0, target.GUID, player.GUID, 100, 1114636288L, HackState.Hacked, 8L)) +// sendResponse(HackMessage(0, target.GUID, player.GUID, 100, 1114636288L, HackState.Hacked, 8L)) completeAction() } else { //continue next tick @@ -2257,6 +2270,23 @@ class WorldSessionActor extends Actor with MDCContextAware { } else if((player.DrawnSlot = held_holsters) != before) { avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.ObjectHeld(player.GUID, player.LastDrawnSlot)) + + + // Ignore non-equipment holsters + //todo: check current suit holster slots? + if(held_holsters >= 0 && held_holsters < 5) { + player.Holsters()(held_holsters).Equipment match { + case Some(unholsteredItem : Equipment) => + if(unholsteredItem.Definition == GlobalDefinitions.remote_electronics_kit) { + // Player has ulholstered a REK - we need to set an atttribute on the REK itself to change the beam/icon colour to the correct one for the player's hack level + avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttribute(unholsteredItem.GUID, 116, GetPlayerHackData().hackLevel)) + } + case None => ; + } + + } + + // Stop using proximity terminals if player unholsters a weapon (which should re-trigger the proximity effect and re-holster the weapon) if(player.VisibleSlots.contains(held_holsters)) { usingMedicalTerminal match { case Some(term_guid) => @@ -2437,7 +2467,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ AvatarImplantMessage(_, _, _, _) => //(player_guid, unk1, unk2, implant) => log.info("AvatarImplantMessage: " + msg) - case msg @ UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType) => + case msg @ UseItemMessage(avatar_guid, item_used_guid, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType) => log.info("UseItem: " + msg) // TODO: Not all fields in the response are identical to source in real packet logs (but seems to be ok) // TODO: Not all incoming UseItemMessage's respond with another UseItemMessage (i.e. doors only send out GenericObjectStateMsg) @@ -2459,9 +2489,8 @@ class WorldSessionActor extends Actor with MDCContextAware { player.Slot(player.DrawnSlot).Equipment match { case Some(tool : SimpleItem) => if(tool.Definition == GlobalDefinitions.remote_electronics_kit) { - //TODO get player hack level (for now, presume 15s in intervals of 4/s) - progressBarValue = Some(-2.66f) - self ! WorldSessionActor.ItemHacking(player, panel, tool.GUID, 2.66f, FinishHackingDoor(panel, 1114636288L)) + progressBarValue = Some(-GetPlayerHackSpeed()) + self ! WorldSessionActor.ItemHacking(player, panel, tool.GUID, GetPlayerHackSpeed(), FinishHacking(panel, 1114636288L)) log.info("Hacking a door~") } case _ => ; @@ -2471,11 +2500,11 @@ class WorldSessionActor extends Actor with MDCContextAware { case Some(obj : Player) => if(obj.isBackpack) { log.info(s"UseItem: $player looting the corpse of $obj") - sendResponse(UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) + sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) accessedContainer = Some(obj) } else if(!unk3) { //potential kit use - continent.GUID(unk1) match { + continent.GUID(item_used_guid) match { case Some(kit : Kit) => player.Find(kit) match { case Some(index) => @@ -2491,7 +2520,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case Some(index) => whenUsedLastKit = System.currentTimeMillis player.Slot(index).Equipment = None //remove from slot immediately; must exist on client for next packet - sendResponse(UseItemMessage(avatar_guid, unk1, object_guid, 0, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) + sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, 0, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) sendResponse(ObjectDeleteMessage(kit.GUID, 0)) taskResolver ! GUIDTask.UnregisterEquipment(kit)(continent.GUID) //TODO better health/damage control workflow @@ -2513,16 +2542,26 @@ class WorldSessionActor extends Actor with MDCContextAware { case Some(item) => log.warn(s"UseItem: looking for Kit to use, but found $item instead") case None => - log.warn(s"UseItem: anticipated a Kit $unk1, but can't find it") + log.warn(s"UseItem: anticipated a Kit $item_used_guid, but can't find it") } } case Some(obj : Locker) => - if(player.Faction == obj.Faction) { + if(obj.Faction != player.Faction && obj.HackedBy.isEmpty) { + player.Slot(player.DrawnSlot).Equipment match { + case Some(tool: SimpleItem) => + if (tool.Definition == GlobalDefinitions.remote_electronics_kit) { + progressBarValue = Some(-GetPlayerHackSpeed()) + self ! WorldSessionActor.ItemHacking(player, obj, tool.GUID, GetPlayerHackSpeed(), FinishHacking(obj, 3212836864L)) + log.info("Hacking a locker") + } + case _ => ; + } + } else if(player.Faction == obj.Faction || !obj.HackedBy.isEmpty) { log.info(s"UseItem: $player accessing a locker") val container = player.Locker accessedContainer = Some(container) - sendResponse(UseItemMessage(avatar_guid, unk1, container.GUID, unk2, unk3, unk4, unk5, unk6, unk7, unk8, 456)) + sendResponse(UseItemMessage(avatar_guid, item_used_guid, container.GUID, unk2, unk3, unk4, unk5, unk6, unk7, unk8, 456)) } else { log.info(s"UseItem: not $player's locker") @@ -2545,7 +2584,7 @@ class WorldSessionActor extends Actor with MDCContextAware { obj.AccessingTrunk = player.GUID accessedContainer = Some(obj) AccessContents(obj) - sendResponse(UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) + sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) } else { log.info(s"UseItem: $obj's trunk is not currently accessible for $player") @@ -2578,14 +2617,29 @@ class WorldSessionActor extends Actor with MDCContextAware { else if(obj.Definition.isInstanceOf[RepairRearmSiloDefinition]) { FindLocalVehicle match { case Some(vehicle) => - sendResponse(UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) - sendResponse(UseItemMessage(avatar_guid, unk1, vehicle.GUID, unk2, unk3, unk4, unk5, unk6, unk7, unk8, vehicle.Definition.ObjectId)) + sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) + sendResponse(UseItemMessage(avatar_guid, item_used_guid, vehicle.GUID, unk2, unk3, unk4, unk5, unk6, unk7, unk8, vehicle.Definition.ObjectId)) case None => log.error("UseItem: expected seated vehicle, but found none") } } else { - sendResponse(UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) + if(obj.Faction != player.Faction && obj.HackedBy.isEmpty) { + player.Slot(player.DrawnSlot).Equipment match { + case Some(tool: SimpleItem) => + if (tool.Definition == GlobalDefinitions.remote_electronics_kit) { + progressBarValue = Some(-GetPlayerHackSpeed()) + self ! WorldSessionActor.ItemHacking(player, obj, tool.GUID, GetPlayerHackSpeed(), FinishHacking(obj, 3212836864L)) + log.info("Hacking a terminal") + } + case _ => ; + } + } else if (obj.Faction == player.Faction || !obj.HackedBy.isEmpty) { + // If hacked only allow access to the faction that hacked it + // Otherwise allow the faction that owns the terminal to use it + sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) + } + } case Some(obj : SpawnTube) => @@ -2599,7 +2653,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case Some(obj) => log.warn(s"UseItem: don't know how to handle $obj; taking a shot in the dark") - sendResponse(UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) + sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) case None => log.error(s"UseItem: can not find object $object_guid") @@ -3423,20 +3477,25 @@ class WorldSessionActor extends Actor with MDCContextAware { } /** - * The process of hacking the `Door` `IFFLock` is completed. - * Pass the message onto the lock and onto the local events system. - * @param target the `IFFLock` belonging to the door that is being hacked + * The process of hacking an object is completed + * Pass the message onto the hackable object and onto the local events system. + * @param target the `Hackable` object that has been hacked * @param unk na; - * used by `HackingMessage` as `unk5` + * used by `HackMessage` as `unk5` * @see `HackMessage` */ //TODO add params here depending on which params in HackMessage are important - //TODO sound should be centered on IFFLock, not on player - private def FinishHackingDoor(target : IFFLock, unk : Long)() : Unit = { - target.Actor ! CommonMessages.Hack(player) - localService ! LocalServiceMessage(continent.Id, LocalAction.TriggerSound(player.GUID, TriggeredSound.HackDoor, player.Position, 30, 0.49803925f)) + private def FinishHacking(target : PlanetSideServerObject with Hackable, unk : Long)() : Unit = { + // Wait for the target actor to set the HackedBy property, otherwise LocalAction.HackTemporarily will not complete properly + import scala.concurrent.ExecutionContext.Implicits.global + ask(target.Actor, CommonMessages.Hack(player))(1 second).mapTo[Boolean].onComplete { + case Success(_) => + localService ! LocalServiceMessage(continent.Id, LocalAction.TriggerSound(player.GUID, target.HackSound, player.Position, 30, 0.49803925f)) localService ! LocalServiceMessage(continent.Id, LocalAction.HackTemporarily(player.GUID, continent, target, unk)) + case scala.util.Failure(_) => log.warn(s"Hack message failed on target guid: ${target.GUID}") } + } + /** * Temporary function that iterates over vehicle permissions and turns them into `PlanetsideAttributeMessage` packets.
@@ -4863,6 +4922,30 @@ class WorldSessionActor extends Actor with MDCContextAware { log.trace("WORLD SEND RAW: " + pkt) sendResponse(RawPacket(pkt)) } + + def GetPlayerHackSpeed(): Float = { + if(player.Certifications.contains(CertificationType.ExpertHacking) || player.Certifications.contains(CertificationType.ElectronicsExpert)) { + 10.64f + } else if(player.Certifications.contains(CertificationType.AdvancedHacking)) { + 5.32f + } else { + 2.66f + } + } + + def GetPlayerHackData(): PlayerHackData = { + if(player.Certifications.contains(CertificationType.ExpertHacking) || player.Certifications.contains(CertificationType.ElectronicsExpert)) { + PlayerHackData(3, 10.64f) + } else if(player.Certifications.contains(CertificationType.AdvancedHacking)) { + PlayerHackData(2, 7.98f) + } else if (player.Certifications.contains(CertificationType.Hacking)) { + PlayerHackData(1, 5.32f) + } else { + PlayerHackData(0, 2.66f) + } + } + + case class PlayerHackData(hackLevel: Int, hackSpeed: Float) } object WorldSessionActor {