From 0a4bac8ab508868bc20be63243137fadd0fd6567 Mon Sep 17 00:00:00 2001 From: FateJH Date: Thu, 12 Oct 2017 21:05:38 -0400 Subject: [PATCH] door hacking now clears in 60s --- .../serverobject/locks/IFFLockControl.scala | 4 +- .../objects/zones/HackClearActor.scala | 87 +++++++++++++++++++ pslogin/src/main/scala/AvatarService.scala | 7 -- pslogin/src/main/scala/LocalService.scala | 30 ++++++- .../src/main/scala/WorldSessionActor.scala | 44 ++++++---- 5 files changed, 143 insertions(+), 29 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/zones/HackClearActor.scala 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 571b69684..e96d1bd34 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 @@ -3,7 +3,6 @@ package net.psforever.objects.serverobject.locks import akka.actor.{Actor, Cancellable} import net.psforever.objects.serverobject.CommonMessages -import net.psforever.objects.serverobject.doors.Door class IFFLockControl(lock : IFFLock) extends Actor { def receive : Receive = { @@ -11,8 +10,7 @@ class IFFLockControl(lock : IFFLock) extends Actor { lock.HackedBy = player case CommonMessages.ClearHack() => lock.HackedBy = None - case _ => - sender ! Door.NoEvent() + case _ => ; } } diff --git a/common/src/main/scala/net/psforever/objects/zones/HackClearActor.scala b/common/src/main/scala/net/psforever/objects/zones/HackClearActor.scala new file mode 100644 index 000000000..b595051e6 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/zones/HackClearActor.scala @@ -0,0 +1,87 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.zones + +import akka.actor.{Actor, Cancellable} +import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} +import net.psforever.packet.game.PlanetSideGUID + +import scala.annotation.tailrec +import scala.concurrent.duration._ + +class HackClearActor() extends Actor { + import HackClearActor._ + private var clearTrigger : Cancellable = DefaultClearer + private var hackedObjects : List[HackEntry] = Nil + //private[this] val log = org.log4s.getLogger + + def receive : Receive = { + case ObjectIsHacked(door, zone, unk1, unk2, time) => + hackedObjects = hackedObjects :+ HackEntry(door, zone, unk1, unk2, time) + if(hackedObjects.size == 1) { + import scala.concurrent.ExecutionContext.Implicits.global + clearTrigger = context.system.scheduler.scheduleOnce(timeout, self, HackClearActor.TryClearHacks()) + } + + case TryClearHacks() => + clearTrigger.cancel + val now : Long = System.nanoTime + //TODO we can just walk across the list of doors and extract only the first few entries + val (unhackObjects, stillHackedObjects) = recursivePartitionHacks(hackedObjects.iterator, now) + hackedObjects = stillHackedObjects + unhackObjects.foreach(entry => { + entry.target.Actor ! CommonMessages.ClearHack() + context.parent ! HackClearActor.ClearTheHack(entry.target.GUID, entry.zone.Id, entry.unk1, entry.unk2) + }) + + if(stillHackedObjects.nonEmpty) { + val short_timeout : FiniteDuration = math.max(1, timeout_time - (now - stillHackedObjects.head.hacked_at_time)) nanoseconds + import scala.concurrent.ExecutionContext.Implicits.global + clearTrigger = context.system.scheduler.scheduleOnce(short_timeout, self, HackClearActor.TryClearHacks()) + } + + case _ => ; + } + + /** + * na + * @param iter na + * @param now na + * @param list na + * @see `List.partition` + * @return a `Tuple` of two `Lists`: + * the entries for all objects that are no longer hacked, + * and the entries for all objects that are still hacked + */ + @tailrec private def recursivePartitionHacks(iter : Iterator[HackEntry], now : Long, list : List[HackEntry] = Nil) : (List[HackEntry], List[HackEntry]) = { + if(!iter.hasNext) { + (list, iter.toList) + } + else { + val entry = iter.next() + if(now - entry.hacked_at_time >= timeout_time) { + recursivePartitionHacks(iter, now, list :+ entry) + } + else { + (list, entry +: iter.toList) + } + } + } +} + +object HackClearActor { + private final val timeout_time : Long = 60000000000L //nanoseconds (1 minute) + private final val timeout : FiniteDuration = timeout_time nanoseconds + + private final val DefaultClearer : Cancellable = new Cancellable() { + override def cancel : Boolean = true + override def isCancelled : Boolean = true + } + + final case class ObjectIsHacked(target : PlanetSideServerObject, zone : Zone, unk1 : Long, unk2 : Long, hacked_at_time : Long = System.nanoTime()) + + final case class ClearTheHack(door_guid : PlanetSideGUID, zone_id : String, unk1 : Long, unk2 : Long) + + private final case class HackEntry(target : PlanetSideServerObject, zone : Zone, unk1 : Long, unk2 : Long, hacked_at_time : Long) + + private final case class TryClearHacks() +} diff --git a/pslogin/src/main/scala/AvatarService.scala b/pslogin/src/main/scala/AvatarService.scala index 0287f2444..5905366f2 100644 --- a/pslogin/src/main/scala/AvatarService.scala +++ b/pslogin/src/main/scala/AvatarService.scala @@ -1,7 +1,6 @@ // Copyright (c) 2017 PSForever import akka.actor.Actor import net.psforever.objects.equipment.Equipment -import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.packet.game.objectcreate.ConstructorData import net.psforever.types.ExoSuitType import net.psforever.packet.game.{PlanetSideGUID, PlayerStateMessageUpstream} @@ -14,7 +13,6 @@ object AvatarAction { //final case class DropItem(pos : Vector3, orient : Vector3, item : PlanetSideGUID) extends Action final case class EquipmentInHand(player_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action final case class EquipmentOnGround(player_guid : PlanetSideGUID, pos : Vector3, orient : Vector3, item : Equipment) extends Action - final case class Hack(player_guid : PlanetSideGUID, target : PlanetSideServerObject, unk1 : Long, unk2 : Long = 8L) extends Action final case class LoadPlayer(player_guid : PlanetSideGUID, pdata : ConstructorData) extends Action // final case class LoadMap(msg : PlanetSideGUID) extends Action // final case class unLoadMap(msg : PlanetSideGUID) extends Action @@ -36,7 +34,6 @@ object AvatarServiceResponse { //final case class DropItem(pos : Vector3, orient : Vector3, item : PlanetSideGUID) extends Response final case class EquipmentInHand(slot : Int, item : Equipment) extends Response final case class EquipmentOnGround(pos : Vector3, orient : Vector3, item : Equipment) extends Response - final case class Hack(target_guid : PlanetSideGUID, unk1 : Long, unk2 : Long = 8L) extends Response final case class LoadPlayer(pdata : ConstructorData) extends Response // final case class unLoadMap() extends Response // final case class LoadMap() extends Response @@ -96,10 +93,6 @@ class AvatarService extends Actor { AvatarEvents.publish( AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarServiceResponse.EquipmentOnGround(pos, orient, obj)) ) - case AvatarAction.Hack(player_guid, target, unk1, unk2) => - AvatarEvents.publish( - AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarServiceResponse.Hack(target.GUID, unk1, unk2)) - ) case AvatarAction.LoadPlayer(player_guid, pdata) => AvatarEvents.publish( AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarServiceResponse.LoadPlayer(pdata)) diff --git a/pslogin/src/main/scala/LocalService.scala b/pslogin/src/main/scala/LocalService.scala index ae014ab25..165016ef3 100644 --- a/pslogin/src/main/scala/LocalService.scala +++ b/pslogin/src/main/scala/LocalService.scala @@ -1,7 +1,8 @@ // Copyright (c) 2017 PSForever import akka.actor.{Actor, Props} +import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.doors.Door -import net.psforever.objects.zones.{DoorCloseActor, Zone} +import net.psforever.objects.zones.{DoorCloseActor, HackClearActor, Zone} import net.psforever.packet.game.PlanetSideGUID object LocalAction { @@ -9,6 +10,8 @@ object LocalAction { final case class DoorOpens(player_guid : PlanetSideGUID, continent : Zone, door : Door) extends Action final case class DoorCloses(player_guid : PlanetSideGUID, door_guid : PlanetSideGUID) extends Action + final case class HackClear(player_guid : PlanetSideGUID, target : PlanetSideServerObject, unk1 : Long, unk2 : Long = 8L) extends Action + final case class HackTemporarily(player_guid : PlanetSideGUID, continent : Zone, target : PlanetSideServerObject, unk1 : Long, unk2 : Long = 8L) extends Action } object LocalServiceResponse { @@ -16,6 +19,8 @@ object LocalServiceResponse { final case class DoorOpens(door_guid : PlanetSideGUID) extends Response final case class DoorCloses(door_guid : PlanetSideGUID) extends Response + final case class HackClear(target_guid : PlanetSideGUID, unk1 : Long, unk2 : Long) extends Response + final case class HackObject(target_guid : PlanetSideGUID, unk1 : Long, unk2 : Long) extends Response } final case class LocalServiceMessage(forChannel : String, actionMessage : LocalAction.Action) @@ -29,6 +34,7 @@ final case class LocalServiceResponse(toChannel : String, avatar_guid : PlanetSi class LocalService extends Actor { //import LocalService._ private val doorCloser = context.actorOf(Props[DoorCloseActor], "local-door-closer") + private val hackClearer = context.actorOf(Props[HackClearActor], "local-hack-clearer") private [this] val log = org.log4s.getLogger override def preStart = { @@ -55,21 +61,39 @@ class LocalService extends Actor { LocalEvents.publish( LocalServiceResponse(s"/$forChannel/LocalEnvironment", player_guid, LocalServiceResponse.DoorOpens(door.GUID)) ) - case LocalAction.DoorCloses(player_guid, door_guid) => LocalEvents.publish( LocalServiceResponse(s"/$forChannel/LocalEnvironment", player_guid, LocalServiceResponse.DoorCloses(door_guid)) ) + case LocalAction.HackClear(player_guid, target, unk1, unk2) => + LocalEvents.publish( + LocalServiceResponse(s"/$forChannel/LocalEnvironment", player_guid, LocalServiceResponse.HackClear(target.GUID, unk1, unk2)) + ) + case LocalAction.HackTemporarily(player_guid, zone, target, unk1, unk2) => + hackClearer ! HackClearActor.ObjectIsHacked(target, zone, unk1, unk2) + LocalEvents.publish( + LocalServiceResponse(s"/$forChannel/Avatar", player_guid, LocalServiceResponse.HackObject(target.GUID, unk1, unk2)) + ) case _ => ; } //response from DoorCloseActor case DoorCloseActor.CloseTheDoor(door_guid, zone_id) => LocalEvents.publish( - LocalServiceResponse(s"/$zone_id/LocalEnvironment", PlanetSideGUID(0), LocalServiceResponse.DoorCloses(door_guid)) + LocalServiceResponse(s"/$zone_id/LocalEnvironment", LocalService.defaultPlayerGUID, LocalServiceResponse.DoorCloses(door_guid)) + ) + + //response from HackClearActor + case HackClearActor.ClearTheHack(target_guid, zone_id, unk1, unk2) => + LocalEvents.publish( + LocalServiceResponse(s"/$zone_id/LocalEnvironment", LocalService.defaultPlayerGUID, LocalServiceResponse.HackClear(target_guid, unk1, unk2)) ) case msg => log.info(s"Unhandled message $msg from $sender") } } + +object LocalService { + final val defaultPlayerGUID : PlanetSideGUID = PlanetSideGUID(0) +} diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 559ea213e..50e8a59ad 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -144,11 +144,6 @@ class WorldSessionActor extends Actor with MDCContextAware { ) } - case AvatarServiceResponse.Hack(target_guid, unk1, unk2) => - if(player.GUID != guid) { - sendResponse(PacketCoding.CreateGamePacket(0, HackMessage(0, target_guid.guid, guid.guid, 100, unk1, 6, unk2))) - } - case AvatarServiceResponse.LoadPlayer(pdata) => if(player.GUID != guid) { sendResponse( @@ -232,6 +227,15 @@ class WorldSessionActor extends Actor with MDCContextAware { case LocalServiceResponse.DoorCloses(door_guid) => //door closes for everyone sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectStateMsg(door_guid, 17))) + + case LocalServiceResponse.HackClear(target_guid, unk1, unk2) => + log.info(s"Clear hack of $target_guid") + sendResponse(PacketCoding.CreateGamePacket(0, HackMessage(0, target_guid.guid, guid.guid, 0, unk1, 7, unk2))) + + case LocalServiceResponse.HackObject(target_guid, unk1, unk2) => + if(player.GUID != guid) { + sendResponse(PacketCoding.CreateGamePacket(0, HackMessage(0, target_guid.guid, guid.guid, 100, unk1, 6, unk2))) + } } case Door.DoorMessage(tplayer, msg, order) => @@ -241,7 +245,7 @@ class WorldSessionActor extends Actor with MDCContextAware { continent.GUID(door_guid) match { case Some(door : Door) => sendResponse(PacketCoding.CreateGamePacket(0, GenericObjectStateMsg(door_guid, 16))) - localService ! LocalServiceMessage (continent.Id, LocalAction.DoorOpens (tplayer.GUID, continent, door) ) + localService ! LocalServiceMessage(continent.Id, LocalAction.DoorOpens (tplayer.GUID, continent, door) ) case _ => log.warn(s"door $door_guid wanted to be opened but could not be found") @@ -578,7 +582,7 @@ class WorldSessionActor extends Actor with MDCContextAware { continent.Actor ! Zone.DropItemOnGround(item, item.Position, item.Orientation) //restore } - case ItemHacking(tplayer, target, tool_guid, delta) => + case ItemHacking(tplayer, target, tool_guid, delta, completeAction, tickAction) => progressBarUpdate.cancel if(progressBarValue.isDefined) { val progressBarVal : Float = progressBarValue.get + delta @@ -594,17 +598,16 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(PacketCoding.CreateGamePacket(0, HackMessage(1, target.GUID.guid, player.GUID.guid, progressBarVal.toInt, 0L, vis, 8L))) if(progressBarVal > 100) { progressBarValue = None - log.info(s"We've hacked the item $target! Now what?") - sendResponse(PacketCoding.CreateGamePacket(0, HackMessage(0, target.GUID.guid, player.GUID.guid, 100, 1114636288, 6, 8L))) - target.Actor ! CommonMessages.Hack(tplayer) - avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.Hack(tplayer.GUID, target, 1114636288)) - //TODO now what? + log.info(s"Hacked a $target") + sendResponse(PacketCoding.CreateGamePacket(0, HackMessage(0, target.GUID.guid, player.GUID.guid, 100, 1114636288L, 6, 8L))) + completeAction() } else { + tickAction.getOrElse(() => Unit)() progressBarValue = Some(progressBarVal) import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global - progressBarUpdate = context.system.scheduler.scheduleOnce(250 milliseconds, self, ItemHacking(tplayer, target, tool_guid, delta)) + progressBarUpdate = context.system.scheduler.scheduleOnce(250 milliseconds, self, ItemHacking(tplayer, target, tool_guid, delta, completeAction)) } } @@ -1052,7 +1055,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case Some(tool : SimpleItem) => if(tool.Definition == GlobalDefinitions.remote_electronics_kit) { progressBarValue = Some(-2.66f) - self ! WorldSessionActor.ItemHacking(player, panel, tool.GUID, 2.66f) + self ! WorldSessionActor.ItemHacking(player, panel, tool.GUID, 2.66f, HackTemporary(panel)) } case _ => ; } @@ -1622,6 +1625,11 @@ class WorldSessionActor extends Actor with MDCContextAware { } } + private def HackTemporary(target : PlanetSideServerObject)() : Unit = { + target.Actor ! CommonMessages.Hack(player) + localService ! LocalServiceMessage(player.Continent, LocalAction.HackTemporarily(player.GUID, continent, target, 1114636288L)) + } + def failWithError(error : String) = { log.error(error) sendResponse(PacketCoding.CreateControlPacket(ConnectionClose())) @@ -1652,8 +1660,12 @@ object WorldSessionActor { private final case class PlayerFailedToLoad(tplayer : Player) private final case class ListAccountCharacters() private final case class SetCurrentAvatar(tplayer : Player) - private final case class ItemHacking(tplayer : Player, target : PlanetSideServerObject, tool_guid : PlanetSideGUID, delta : Float) - + private final case class ItemHacking(tplayer : Player, + target : PlanetSideServerObject, + tool_guid : PlanetSideGUID, + delta : Float, + completeAction : () => Unit, + tickAction : Option[() => Unit] = None) /** * A placeholder `Cancellable` object. */