From afb1f4b2792a561695c1397f1f350ff02ad288fe Mon Sep 17 00:00:00 2001 From: "Jason_DiDonato@yahoo.com" Date: Tue, 18 May 2021 13:59:50 -0400 Subject: [PATCH] ward against spawning multiple LLU's during normal/wrested base capture --- .../serverobject/llu/CaptureFlagSocket.scala | 14 +- .../serverobject/structures/Building.scala | 7 +- .../local/support/CaptureFlagManager.scala | 29 ++-- .../local/support/HackCaptureActor.scala | 126 +++++++++++------- 4 files changed, 111 insertions(+), 65 deletions(-) diff --git a/src/main/scala/net/psforever/objects/serverobject/llu/CaptureFlagSocket.scala b/src/main/scala/net/psforever/objects/serverobject/llu/CaptureFlagSocket.scala index 3ced71d97..e68dfa600 100644 --- a/src/main/scala/net/psforever/objects/serverobject/llu/CaptureFlagSocket.scala +++ b/src/main/scala/net/psforever/objects/serverobject/llu/CaptureFlagSocket.scala @@ -11,7 +11,19 @@ import net.psforever.types.Vector3 * It is used as a position reference for spawning the LLU in the correct location when the base is hacked * @param tDef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields */ -class CaptureFlagSocket(tDef: CaptureFlagSocketDefinition) extends Amenity { +class CaptureFlagSocket(tDef: CaptureFlagSocketDefinition) + extends Amenity { + private var spawnedCaptureFlag: Option[CaptureFlag] = None + + def captureFlag: Option[CaptureFlag] = spawnedCaptureFlag + + def captureFlag_=(flag: CaptureFlag): Option[CaptureFlag] = captureFlag_=(Some(flag)) + + def captureFlag_=(flag: Option[CaptureFlag]): Option[CaptureFlag] = { + spawnedCaptureFlag = flag + captureFlag + } + def Definition : CaptureFlagSocketDefinition = tDef } diff --git a/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala b/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala index 940fc477f..107a20530 100644 --- a/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala +++ b/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala @@ -129,7 +129,12 @@ class Building( } def GetFlagSocket: Option[CaptureFlagSocket] = this.Amenities.find(_.Definition == GlobalDefinitions.llm_socket).asInstanceOf[Option[CaptureFlagSocket]] - def GetFlag: Option[CaptureFlag] = this.Amenities.find(_.Definition == GlobalDefinitions.capture_flag).asInstanceOf[Option[CaptureFlag]] + def GetFlag: Option[CaptureFlag] = { + GetFlagSocket match { + case Some(socket) => socket.captureFlag + case None => None + } + } def HackableAmenities: List[Amenity with Hackable] = { Amenities.filter(x => x.isInstanceOf[Hackable]).map(x => x.asInstanceOf[Amenity with Hackable]) diff --git a/src/main/scala/net/psforever/services/local/support/CaptureFlagManager.scala b/src/main/scala/net/psforever/services/local/support/CaptureFlagManager.scala index 67913bb29..d6d97263b 100644 --- a/src/main/scala/net/psforever/services/local/support/CaptureFlagManager.scala +++ b/src/main/scala/net/psforever/services/local/support/CaptureFlagManager.scala @@ -102,6 +102,7 @@ class CaptureFlagManager(val taskResolver: ActorRef, zone: Zone) extends Actor{ ) // Add the flag as an amenity and track it internally + socket.captureFlag = flag TrackFlag(flag) taskResolver ! CallBackForTask( @@ -128,15 +129,19 @@ class CaptureFlagManager(val taskResolver: ActorRef, zone: Zone) extends Actor{ HandleFlagDespawn(flag) case CaptureFlagManager.Lost(flag: CaptureFlag, reason: CaptureFlagLostReasonEnum) => - val message = reason match { + reason match { case CaptureFlagLostReasonEnum.Resecured => - CaptureFlagChatMessageStrings.CTF_Failed_SourceResecured(flag.Owner.asInstanceOf[Building]) - case CaptureFlagLostReasonEnum.TimedOut => - CaptureFlagChatMessageStrings.CTF_Failed_TimedOut(flag.Owner.asInstanceOf[Building].Name, flag.Target) + ChatBroadcast( + flag.Zone, + CaptureFlagChatMessageStrings.CTF_Failed_SourceResecured(flag.Owner.asInstanceOf[Building]) + ) + case CaptureFlagLostReasonEnum.TimedOut => + ChatBroadcast( + flag.Zone, + CaptureFlagChatMessageStrings.CTF_Failed_TimedOut(flag.Owner.asInstanceOf[Building].Name, flag.Target) + ) + case CaptureFlagLostReasonEnum.Ended => ; /* no message */ } - - ChatBroadcast(flag.Zone, message) - HandleFlagDespawn(flag) case CaptureFlagManager.PickupFlag(flag: CaptureFlag, player: Player) => @@ -173,14 +178,13 @@ class CaptureFlagManager(val taskResolver: ActorRef, zone: Zone) extends Actor{ } private def HandleFlagDespawn(flag: CaptureFlag): Unit = { + // Remove the flag as an amenity + flag.Owner.asInstanceOf[Building].GetFlagSocket.get.captureFlag = None + UntrackFlag(flag) // Unregister LLU from clients, flag.Zone.LocalEvents ! LocalServiceMessage(flag.Zone.id, LocalAction.LluDespawned(PlanetSideGUID(-1), flag)) - // Then unregister it from the GUID pool taskResolver ! TaskResolver.GiveTask(GUIDTask.UnregisterObjectTask(flag)(flag.Zone.GUID).task) - - // Remove the flag as an amenity - UntrackFlag(flag) } private def ChatBroadcast(zone: Zone, message: String, fanfare: Boolean = true): Unit = { @@ -271,6 +275,7 @@ object CaptureFlagChatMessageStrings { case PlanetSideEmpire.TR => "TerranRepublic" case PlanetSideEmpire.NC => "NewConglomerate" case PlanetSideEmpire.VS => "VanuSovereigncy" // Yes, this is wrong. It is like that in packet captures. + case _ => "TerranRepublic" //todo: BO message? } } } @@ -278,5 +283,5 @@ object CaptureFlagChatMessageStrings { object CaptureFlagLostReasonEnum extends Enumeration { type CaptureFlagLostReasonEnum = Value - val Resecured, TimedOut = Value + val Resecured, TimedOut, Ended = Value } 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 b86523299..cb3a23901 100644 --- a/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala +++ b/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala @@ -31,42 +31,36 @@ class HackCaptureActor(val taskResolver: ActorRef) extends Actor { def receive: Receive = { case HackCaptureActor.StartCaptureTerminalHack(target, zone, unk1, unk2, startTime) => log.trace(s"StartCaptureTerminalHack: ${target.GUID} is hacked.") - - val duration = target.Definition match { - case GlobalDefinitions.capture_terminal => - // Base CC - 15 minutes - case GlobalDefinitions.secondary_capture => - // Tower CC - 1 nanosecond - case GlobalDefinitions.vanu_control_console => - // Cavern CC - 10 minutes - } - - target.HackedBy match { - case Some(hackInfo) => - target.HackedBy = hackInfo.Duration(duration.toNanos) - case None => - log.error(s"Initial $target hack information is missing") - } - - hackedObjects.find(_.target == target) match { - case Some(_) => - log.trace( - s"StartCaptureTerminalHack: ${target.GUID} was already hacked - removing it from the hacked objects list before re-adding it." - ) - hackedObjects = hackedObjects.filterNot(x => x.target == target) - case _ => ; - } - - hackedObjects = hackedObjects :+ HackCaptureActor.HackEntry(target, zone, unk1, unk2, duration, startTime) - - // Restart the timer, in case this is the first object in the hacked objects list or the object was removed and re-added - RestartTimer() - - NotifyHackStateChange(target, isResecured = false) - TrySpawnCaptureFlag(target) + val duration = target.Definition match { + case GlobalDefinitions.capture_terminal => + // Base CC + 15 minutes + case GlobalDefinitions.secondary_capture => + // Tower CC + 1 nanosecond + case GlobalDefinitions.vanu_control_console => + // Cavern CC + 10 minutes + } + target.HackedBy match { + case Some(hackInfo) => + target.HackedBy = hackInfo.Duration(duration.toNanos) + case None => + log.error(s"Initial $target hack information is missing") + } + hackedObjects.find(_.target == target) match { + case Some(_) => + log.trace( + s"StartCaptureTerminalHack: ${target.GUID} was already hacked - removing it from the hacked objects list before re-adding it." + ) + hackedObjects = hackedObjects.filterNot(x => x.target == target) + case _ => ; + } + hackedObjects = hackedObjects :+ HackCaptureActor.HackEntry(target, zone, unk1, unk2, duration, startTime) + // Restart the timer, in case this is the first object in the hacked objects list or the object was removed and re-added + RestartTimer() + NotifyHackStateChange(target, isResecured = false) + TrySpawnCaptureFlag(target) case HackCaptureActor.ProcessCompleteHacks() => log.trace("Processing complete hacks") @@ -134,27 +128,57 @@ class HackCaptureActor(val taskResolver: ActorRef) extends Actor { case _ => ; } - private def TrySpawnCaptureFlag(terminal: CaptureTerminal): Unit = { + private def TrySpawnCaptureFlag(terminal: CaptureTerminal): Boolean = { // Handle LLUs if the base contains a LLU socket // If there are no neighbouring bases belonging to the hacking faction this will be handled as a regular timed hack (e.g. neutral base in enemy territory) - val owner = terminal.Owner.asInstanceOf[Building] - val hackingFaction = HackCaptureActor.GetHackingFaction(terminal).get - val hackingFactionNeighbourBases = owner.Neighbours(hackingFaction) - - hackingFactionNeighbourBases match { - case Some(neighbours) => - if(owner.IsCtfBase) { - // Find a random neighbouring base matching the hacking faction - val targetBase = neighbours.toVector((new Random).nextInt(neighbours.size)) - - // Request LLU is created by CaptureFlagActor via LocalService - terminal.Zone.LocalEvents ! CaptureFlagManager.SpawnCaptureFlag(terminal, targetBase, hackingFaction) + terminal.Owner match { + case owner: Building if owner.IsCtfBase => + val socket = owner.GetFlagSocket.get + val flag = socket.captureFlag + val owningFaction = owner.Faction + val hackingFaction = HackCaptureActor.GetHackingFaction(terminal).get + owner.Neighbours(hackingFaction) match { + case Some(neighbours) => + if (flag.isEmpty) { + log.info(s"An LLU is being spawned for facility ${owner.Name} by $hackingFaction") + spawnCaptureFlag(neighbours, terminal, hackingFaction) + true + } else if (hackingFaction != flag.get.Faction) { + log.info(s"$hackingFaction is overriding the ongoing LLU hack of facility ${owner.Name} by ${flag.get.Faction}") + terminal.Zone.LocalEvents ! CaptureFlagManager.Lost(flag.get, CaptureFlagLostReasonEnum.Ended) + NotifyHackStateChange(terminal, isResecured = false) + RestartTimer() + spawnCaptureFlag(neighbours, terminal, hackingFaction) + true + } else if (hackingFaction == owningFaction) { + log.error(s"Owning faction and hacking faction match for facility ${owner.Name}; should we be resecuring instead?") + false + } else { + log.warn(s"LLU hack of facility ${owner.Name} by $hackingFaction in progress - no change") + false + } + case None => ; + log.info(s"Couldn't find any neighbouring $hackingFaction facilities of ${owner.Name} for LLU hack") + false } - case None => - log.info("Couldn't find any neighbouring bases for LLU hack.") + + case thing => + log.error(s"Capture terminal has unexpected owner - $thing - that is not a facility") + false } } + private def spawnCaptureFlag( + neighbours: Set[Building], + terminal: CaptureTerminal, + hackingFaction: PlanetSideEmpire.Value + ): Unit = { + // Find a random neighbouring base matching the hacking faction + val targetBase = neighbours.toVector((new Random).nextInt(neighbours.size)) + // Request LLU is created by CaptureFlagActor via LocalService + terminal.Zone.LocalEvents ! CaptureFlagManager.SpawnCaptureFlag(terminal, targetBase, hackingFaction) + } + private def NotifyHackStateChange(terminal: CaptureTerminal, isResecured: Boolean): Unit = { val attribute_value = HackCaptureActor.GetHackUpdateAttributeValue(terminal, isResecured)