periodic message while the llu is active, whether or not there is a carrier, until the hack is finished

This commit is contained in:
Fate-JH 2024-08-19 19:35:08 -04:00
parent a4084e52ce
commit e4275cf298
2 changed files with 115 additions and 73 deletions

View file

@ -22,12 +22,13 @@ import scala.concurrent.duration.DurationInt
* Responsible for handling capture flag related lifecycles * Responsible for handling capture flag related lifecycles
*/ */
class CaptureFlagManager(zone: Zone) extends Actor { class CaptureFlagManager(zone: Zone) extends Actor {
import CaptureFlagManager.CaptureFlagEntry
private[this] val log = org.log4s.getLogger(self.path.name) private[this] val log = org.log4s.getLogger(self.path.name)
private var galaxyService: ActorRef = ActorRef.noSender private var galaxyService: ActorRef = ActorRef.noSender
private var mapUpdateTick: Cancellable = Default.Cancellable private var mapUpdateTick: Cancellable = Default.Cancellable
/** An internally tracked list of cached flags, to avoid querying the amenity owner each second for flag lookups. */ /** An internally tracked list of cached flags, to avoid querying the amenity owner each second for flag lookups. */
private var flags: List[CaptureFlag] = Nil private var flags: List[CaptureFlagEntry] = Nil
ServiceManager.serviceManager ! Lookup("galaxy") ServiceManager.serviceManager ! Lookup("galaxy")
@ -39,7 +40,6 @@ class CaptureFlagManager(zone: Zone) extends Actor {
DoMapUpdate() DoMapUpdate()
case CaptureFlagManager.SpawnCaptureFlag(capture_terminal, target, hackingFaction) => case CaptureFlagManager.SpawnCaptureFlag(capture_terminal, target, hackingFaction) =>
val zone = capture_terminal.Zone
val socket = capture_terminal.Owner.asInstanceOf[Building].GetFlagSocket.get val socket = capture_terminal.Owner.asInstanceOf[Building].GetFlagSocket.get
// Override CC message when looked at // Override CC message when looked at
zone.LocalEvents ! LocalServiceMessage( zone.LocalEvents ! LocalServiceMessage(
@ -62,16 +62,16 @@ class CaptureFlagManager(zone: Zone) extends Actor {
socket.captureFlag = flag socket.captureFlag = flag
TrackFlag(flag) TrackFlag(flag)
TaskWorkflow.execute(WorldSession.CallBackForTask( TaskWorkflow.execute(WorldSession.CallBackForTask(
GUIDTask.registerObject(socket.Zone.GUID, flag), GUIDTask.registerObject(zone.GUID, flag),
socket.Zone.LocalEvents, zone.LocalEvents,
LocalServiceMessage( LocalServiceMessage(
socket.Zone.id, zone.id,
LocalAction.LluSpawned(Service.defaultPlayerGUID, flag) LocalAction.LluSpawned(Service.defaultPlayerGUID, flag)
) )
)) ))
// Broadcast chat message for LLU spawn // Broadcast chat message for LLU spawn
val owner = flag.Owner.asInstanceOf[Building] val owner = flag.Owner.asInstanceOf[Building]
ChatBroadcast(flag.Zone, CaptureFlagChatMessageStrings.CTF_FlagSpawned(owner, flag.Target)) CaptureFlagManager.ChatBroadcast(zone, CaptureFlagChatMessageStrings.CTF_FlagSpawned(owner, flag.Target))
case CaptureFlagManager.Captured(flag: CaptureFlag) => case CaptureFlagManager.Captured(flag: CaptureFlag) =>
val name = flag.Carrier match { val name = flag.Carrier match {
@ -79,29 +79,29 @@ class CaptureFlagManager(zone: Zone) extends Actor {
case None => "A soldier" case None => "A soldier"
} }
// Trigger Install sound // Trigger Install sound
flag.Zone.LocalEvents ! LocalServiceMessage(flag.Zone.id, LocalAction.TriggerSound(PlanetSideGUID(-1), TriggeredSound.LLUInstall, flag.Target.CaptureTerminal.get.Position, 20, 0.8000001f)) zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.TriggerSound(PlanetSideGUID(-1), TriggeredSound.LLUInstall, flag.Target.CaptureTerminal.get.Position, 20, 0.8000001f))
// Broadcast capture chat message // Broadcast capture chat message
ChatBroadcast(flag.Zone, CaptureFlagChatMessageStrings.CTF_Success(name, flag.Faction, flag.Owner.asInstanceOf[Building].Name)) CaptureFlagManager.ChatBroadcast(zone, CaptureFlagChatMessageStrings.CTF_Success(name, flag.Faction, flag.Owner.asInstanceOf[Building].Name))
// Despawn flag // Despawn flag
HandleFlagDespawn(flag) HandleFlagDespawn(flag)
case CaptureFlagManager.Lost(flag: CaptureFlag, reason: CaptureFlagLostReasonEnum) => case CaptureFlagManager.Lost(flag: CaptureFlag, reason: CaptureFlagLostReasonEnum) =>
reason match { reason match {
case CaptureFlagLostReasonEnum.Resecured => case CaptureFlagLostReasonEnum.Resecured =>
ChatBroadcast( CaptureFlagManager.ChatBroadcast(
flag.Zone, zone,
CaptureFlagChatMessageStrings.CTF_Failed_SourceResecured(flag.Owner.asInstanceOf[Building].Name, flag.Faction) CaptureFlagChatMessageStrings.CTF_Failed_SourceResecured(flag.Owner.asInstanceOf[Building].Name, flag.Faction)
) )
case CaptureFlagLostReasonEnum.TimedOut => case CaptureFlagLostReasonEnum.TimedOut =>
val building = flag.Owner.asInstanceOf[Building] val building = flag.Owner.asInstanceOf[Building]
ChatBroadcast( CaptureFlagManager.ChatBroadcast(
flag.Zone, zone,
CaptureFlagChatMessageStrings.CTF_Failed_TimedOut(building.Name, flag.Target.Name, flag.Faction) CaptureFlagChatMessageStrings.CTF_Failed_TimedOut(building.Name, flag.Target.Name, flag.Faction)
) )
case CaptureFlagLostReasonEnum.FlagLost => case CaptureFlagLostReasonEnum.FlagLost =>
val building = flag.Owner.asInstanceOf[Building] val building = flag.Owner.asInstanceOf[Building]
ChatBroadcast( CaptureFlagManager.ChatBroadcast(
flag.Zone, zone,
CaptureFlagChatMessageStrings.CTF_Failed_FlagLost(building.Name, flag.Faction) CaptureFlagChatMessageStrings.CTF_Failed_FlagLost(building.Name, flag.Faction)
) )
case CaptureFlagLostReasonEnum.Ended => case CaptureFlagLostReasonEnum.Ended =>
@ -110,11 +110,14 @@ class CaptureFlagManager(zone: Zone) extends Actor {
HandleFlagDespawn(flag) HandleFlagDespawn(flag)
case CaptureFlagManager.PickupFlag(flag: CaptureFlag, player: Player) => case CaptureFlagManager.PickupFlag(flag: CaptureFlag, player: Player) =>
val continent = flag.Zone
flag.Carrier = Some(player) flag.Carrier = Some(player)
continent.LocalEvents ! LocalServiceMessage(continent.id, LocalAction.SendPacket(ObjectAttachMessage(player.GUID, flag.GUID, 252))) zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.SendPacket(ObjectAttachMessage(player.GUID, flag.GUID, 252)))
continent.LocalEvents ! LocalServiceMessage(continent.id, LocalAction.TriggerSound(PlanetSideGUID(-1), TriggeredSound.LLUPickup, player.Position, 15, volume = 0.8f)) zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.TriggerSound(PlanetSideGUID(-1), TriggeredSound.LLUPickup, player.Position, 15, volume = 0.8f))
ChatBroadcast(flag.Zone, CaptureFlagChatMessageStrings.CTF_FlagPickedUp(player.Name, player.Faction, flag.Owner.asInstanceOf[Building].Name), fanfare = false) CaptureFlagManager.ChatBroadcast(
zone,
CaptureFlagChatMessageStrings.CTF_FlagPickedUp(player.Name, player.Faction, flag.Owner.asInstanceOf[Building].Name),
fanfare = false
)
case CaptureFlagManager.DropFlag(flag: CaptureFlag) => case CaptureFlagManager.DropFlag(flag: CaptureFlag) =>
flag.Carrier match { flag.Carrier match {
@ -125,9 +128,13 @@ class CaptureFlagManager(zone: Zone) extends Actor {
// Remove attached player from flag // Remove attached player from flag
flag.Carrier = None flag.Carrier = None
// Send drop packet // Send drop packet
flag.Zone.LocalEvents ! LocalServiceMessage(flag.Zone.id, LocalAction.SendPacket(ObjectDetachMessage(player.GUID, flag.GUID, player.Position, 0, 0, 0))) zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.SendPacket(ObjectDetachMessage(player.GUID, flag.GUID, player.Position, 0, 0, 0)))
// Send dropped chat message // Send dropped chat message
ChatBroadcast(flag.Zone, CaptureFlagChatMessageStrings.CTF_FlagDropped(player.Name, player.Faction, flag.Owner.asInstanceOf[Building].Name), fanfare = false) CaptureFlagManager.ChatBroadcast(
zone,
CaptureFlagChatMessageStrings.CTF_FlagDropped(player.Name, player.Faction, flag.Owner.asInstanceOf[Building].Name),
fanfare = false
)
HandleFlagDespawn(flag) HandleFlagDespawn(flag)
// Register LLU object create task and callback to create on clients // Register LLU object create task and callback to create on clients
val replacementLlu = CaptureFlag.Constructor( val replacementLlu = CaptureFlag.Constructor(
@ -142,10 +149,10 @@ class CaptureFlagManager(zone: Zone) extends Actor {
socket.captureFlag = replacementLlu socket.captureFlag = replacementLlu
TrackFlag(replacementLlu) TrackFlag(replacementLlu)
TaskWorkflow.execute(WorldSession.CallBackForTask( TaskWorkflow.execute(WorldSession.CallBackForTask(
GUIDTask.registerObject(socket.Zone.GUID, replacementLlu), GUIDTask.registerObject(zone.GUID, replacementLlu),
socket.Zone.LocalEvents, zone.LocalEvents,
LocalServiceMessage( LocalServiceMessage(
socket.Zone.id, zone.id,
LocalAction.LluSpawned(Service.defaultPlayerGUID, replacementLlu) LocalAction.LluSpawned(Service.defaultPlayerGUID, replacementLlu)
) )
)) ))
@ -158,33 +165,47 @@ class CaptureFlagManager(zone: Zone) extends Actor {
} }
private def DoMapUpdate(): Unit = { private def DoMapUpdate(): Unit = {
val flagInfo = flags.map(flag => val events = zone.LocalEvents
val zoneId = zone.id
val flagInfo = flags.map { case entry @ CaptureFlagManager.CaptureFlagEntry(flag) =>
val owner = flag.Owner.asInstanceOf[Building]
val pos = flag.Position
val hackTimeRemaining = owner.infoUpdateMessage().hack_time_remaining
val nextMessageAfterMinutes = CaptureFlagManager.CaptureFlagCountdownMessages(entry.currentMessageIndex)
if (hackTimeRemaining < nextMessageAfterMinutes.minutes.toMillis) {
entry.currentMessageIndex += 1
val msg = CaptureFlagManager.ComposeWarningMessage(flag, owner.Name, nextMessageAfterMinutes)
//can't use ChatBroadcast(...) because the message contents are wide
events ! LocalServiceMessage(zoneId, LocalAction.SendResponse(
ChatMsg(ChatMessageType.UNK_229, wideContents = true, "", msg, None)
))
}
FlagInfo( FlagInfo(
u1 = 0, u1 = 0,
owner_map_id = flag.Owner.asInstanceOf[Building].MapId, owner_map_id = owner.MapId,
target_map_id = flag.Target.MapId, target_map_id = flag.Target.MapId,
x = flag.Position.x, x = pos.x,
y = flag.Position.y, y = pos.y,
hack_time_remaining = flag.Owner.asInstanceOf[Building].infoUpdateMessage().hack_time_remaining, hack_time_remaining = hackTimeRemaining,
is_monolith_unit = false is_monolith_unit = false
) )
) }
galaxyService ! GalaxyServiceMessage(GalaxyAction.FlagMapUpdate(CaptureFlagUpdateMessage(zone.Number, flagInfo))) galaxyService ! GalaxyServiceMessage(GalaxyAction.FlagMapUpdate(CaptureFlagUpdateMessage(zone.Number, flagInfo)))
} }
private def TrackFlag(flag: CaptureFlag): Unit = { private def TrackFlag(flag: CaptureFlag): Unit = {
flag.Owner.Amenities = flag flag.Owner.Amenities = flag
flags = flags :+ flag flags = flags :+ CaptureFlagEntry(flag)
if (mapUpdateTick.isCancelled) { if (mapUpdateTick.isCancelled) {
// Start sending map updates periodically // Start sending map updates periodically
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
mapUpdateTick = context.system.scheduler.scheduleAtFixedRate(0 seconds, 1 second, self, CaptureFlagManager.MapUpdate()) mapUpdateTick = context.system.scheduler.scheduleAtFixedRate(initialDelay = 0 seconds, interval = 1 second, self, CaptureFlagManager.MapUpdate())
} }
} }
private def UntrackFlag(flag: CaptureFlag): Unit = { private def UntrackFlag(flag: CaptureFlag): Unit = {
flag.Owner.RemoveAmenity(flag) flag.Owner.RemoveAmenity(flag)
flags = flags.filterNot(x => x eq flag) flags = flags.filterNot(x => x.flag eq flag)
if (flags.isEmpty) { if (flags.isEmpty) {
mapUpdateTick.cancel() mapUpdateTick.cancel()
DoMapUpdate() DoMapUpdate()
@ -192,7 +213,6 @@ class CaptureFlagManager(zone: Zone) extends Actor {
} }
private def HandleFlagDespawn(flag: CaptureFlag): Unit = { private def HandleFlagDespawn(flag: CaptureFlag): Unit = {
val zone = flag.Zone
// Remove the flag as an amenity // Remove the flag as an amenity
flag.Owner.asInstanceOf[Building].GetFlagSocket.get.captureFlag = None flag.Owner.asInstanceOf[Building].GetFlagSocket.get.captureFlag = None
UntrackFlag(flag) UntrackFlag(flag)
@ -201,6 +221,23 @@ class CaptureFlagManager(zone: Zone) extends Actor {
// Then unregister it from the GUID pool // Then unregister it from the GUID pool
TaskWorkflow.execute(GUIDTask.unregisterObject(zone.GUID, flag)) TaskWorkflow.execute(GUIDTask.unregisterObject(zone.GUID, flag))
} }
}
object CaptureFlagManager {
sealed trait Command
final case class SpawnCaptureFlag(capture_terminal: CaptureTerminal, target: Building, hackingFaction: PlanetSideEmpire.Value) extends Command
final case class PickupFlag(flag: CaptureFlag, player: Player) extends Command
final case class DropFlag(flag: CaptureFlag) extends Command
final case class Captured(flag: CaptureFlag) extends Command
final case class Lost(flag: CaptureFlag, reason: CaptureFlagLostReasonEnum) extends Command
final case class MapUpdate()
private case class CaptureFlagEntry(flag: CaptureFlag) {
var currentMessageIndex: Int = 0
}
val CaptureFlagCountdownMessages: Seq[Int] = Seq(10, 5, 2, 1, 0)
private def ChatBroadcast(zone: Zone, message: String, fanfare: Boolean = true): Unit = { private def ChatBroadcast(zone: Zone, message: String, fanfare: Boolean = true): Unit = {
//todo use UNK_222 sometimes //todo use UNK_222 sometimes
@ -217,17 +254,22 @@ class CaptureFlagManager(zone: Zone) extends Actor {
) )
) )
} }
}
object CaptureFlagManager { private def ComposeWarningMessage(flag: CaptureFlag, buildingName: String, minutesLeft: Int): String = {
sealed trait Command import CaptureFlagChatMessageStrings._
val carrier = flag.Carrier
final case class SpawnCaptureFlag(capture_terminal: CaptureTerminal, target: Building, hackingFaction: PlanetSideEmpire.Value) extends Command val hasCarrier = carrier.nonEmpty
final case class PickupFlag(flag: CaptureFlag, player: Player) extends Command minutesLeft match {
final case class DropFlag(flag: CaptureFlag) extends Command case 1 if hasCarrier =>
final case class Captured(flag: CaptureFlag) extends Command CTF_Warning_Carrier1Min(carrier.get.Name, flag.Faction, buildingName, flag.Target.Name)
final case class Lost(flag: CaptureFlag, reason: CaptureFlagLostReasonEnum) extends Command case 1 =>
final case class MapUpdate() CTF_Warning_NoCarrier1Min(buildingName, flag.Faction, flag.Target.Name)
case time if hasCarrier =>
CTF_Warning_Carrier(carrier.get.Name, flag.Faction, buildingName, flag.Target.Name, time)
case time =>
CTF_Warning_NoCarrier(buildingName, flag.Faction, flag.Target.Name, time)
}
}
} }
object CaptureFlagChatMessageStrings { object CaptureFlagChatMessageStrings {
@ -263,55 +305,55 @@ object CaptureFlagChatMessageStrings {
// @CTF_FlagPickedUp=%1 of the %2 picked up %3's LLU // @CTF_FlagPickedUp=%1 of the %2 picked up %3's LLU
/** {player.Name} of the {player.Faction} picked up {ownerName}'s LLU */ /** {player.Name} of the {player.Faction} picked up {ownerName}'s LLU */
def CTF_FlagPickedUp(playerName: String, playerFaction: PlanetSideEmpire.Value, ownerName: String): String = private[support] def CTF_FlagPickedUp(playerName: String, playerFaction: PlanetSideEmpire.Value, ownerName: String): String =
s"@CTF_FlagPickedUp^$playerName~^@${GetFactionString(playerFaction)}~^@$ownerName~" s"@CTF_FlagPickedUp^$playerName~^@${GetFactionString(playerFaction)}~^@$ownerName~"
// @CTF_FlagDropped=%1 of the %2 dropped %3's LLU // @CTF_FlagDropped=%1 of the %2 dropped %3's LLU
/** {playerName} of the {faction} dropped {facilityName}'s LLU */ /** {playerName} of the {faction} dropped {facilityName}'s LLU */
def CTF_FlagDropped(playerName: String, playerFaction: PlanetSideEmpire.Value, ownerName: String): String = private[support] def CTF_FlagDropped(playerName: String, playerFaction: PlanetSideEmpire.Value, ownerName: String): String =
s"@CTF_FlagDropped^$playerName~^@${GetFactionString(playerFaction)}~^@$ownerName~" s"@CTF_FlagDropped^$playerName~^@${GetFactionString(playerFaction)}~^@$ownerName~"
// @CTF_Warning_Carrier=%1's LLU is in the field.\nThe %2 must take it to %3 within %4 minutes! // @CTF_Warning_Carrier=%1's LLU is in the field.\nThe %2 must take it to %3 within %4 minutes!
/** {facilityName}'s LLU is in the field.\nThe {faction} must take it to {targetFacilityName} within {time} minutes! */ /** {facilityName}'s LLU is in the field.\nThe {faction} must take it to {targetFacilityName} within {time} minutes! */
def CTF_Warning_Carrier( private[support] def CTF_Warning_Carrier(
playerName:String, playerName:String,
playerFaction: PlanetSideEmpire.Value, playerFaction: PlanetSideEmpire.Value,
facilityName: String, facilityName: String,
targetFacilityName: String, targetFacilityName: String,
time: Int time: Int
): String = { ): String = {
s"@CTF_Warning_Carrier^$playerName~^@${GetFactionString(playerFaction)}~^@$facilityName~^@$targetFacilityName~^@$time~" s"@CTF_Warning_Carrier^$playerName~^@${GetFactionString(playerFaction)}~^@$facilityName~^@$targetFacilityName~^@$time~"
} }
// @CTF_Warning_Carrier1Min=%1 of the %2 has %3's LLU.\nIt must be taken to %4 within the next minute! // @CTF_Warning_Carrier1Min=%1 of the %2 has %3's LLU.\nIt must be taken to %4 within the next minute!
/** {playerName} of the {faction} has {facilityName}'s LLU.\nIt must be taken to {targetFacilityName} within the next minute! */ /** {playerName} of the {faction} has {facilityName}'s LLU.\nIt must be taken to {targetFacilityName} within the next minute! */
def CTF_Warning_Carrier1Min( private[support] def CTF_Warning_Carrier1Min(
playerName:String, playerName:String,
playerFaction: PlanetSideEmpire.Value, playerFaction: PlanetSideEmpire.Value,
facilityName: String, facilityName: String,
targetFacilityName: String targetFacilityName: String
): String = { ): String = {
s"@CTF_Warning_Carrier1Min^$playerName~^@${GetFactionString(playerFaction)}~^@$facilityName~^@$targetFacilityName~" s"@CTF_Warning_Carrier1Min^$playerName~^@${GetFactionString(playerFaction)}~^@$facilityName~^@$targetFacilityName~"
} }
// @CTF_Warning_NoCarrier=%1's LLU is in the field.\nThe %2 must take it to %3 within %4 minutes! // @CTF_Warning_NoCarrier=%1's LLU is in the field.\nThe %2 must take it to %3 within %4 minutes!
/** {facilityName}'s LLU is in the field.\nThe {faction} must take it to {targetFacilityName} within {time} minute! */ /** {facilityName}'s LLU is in the field.\nThe {faction} must take it to {targetFacilityName} within {time} minute! */
def CTF_Warning_NoCarrier( private[support] def CTF_Warning_NoCarrier(
facilityName: String, facilityName: String,
playerFaction: PlanetSideEmpire.Value, playerFaction: PlanetSideEmpire.Value,
targetFacilityName: String, targetFacilityName: String,
time: Int time: Int
): String = { ): String = {
s"@CTF_Warning_NoCarrier^@$facilityName~^@${GetFactionString(playerFaction)}~^@$targetFacilityName~^$time~" s"@CTF_Warning_NoCarrier^@$facilityName~^@${GetFactionString(playerFaction)}~^@$targetFacilityName~^$time~"
} }
// @CTF_Warning_NoCarrier1Min=%1's LLU is in the field.\nThe %2 must take it to %3 within the next minute! // @CTF_Warning_NoCarrier1Min=%1's LLU is in the field.\nThe %2 must take it to %3 within the next minute!
/** {facilityName}'s LLU is in the field.\nThe {faction} must take it to {targetFacilityName} within the next minute! */ /** {facilityName}'s LLU is in the field.\nThe {faction} must take it to {targetFacilityName} within the next minute! */
def CTF_Warning_NoCarrier1Min( private[support] def CTF_Warning_NoCarrier1Min(
facilityName: String, facilityName: String,
playerFaction: PlanetSideEmpire.Value, playerFaction: PlanetSideEmpire.Value,
targetFacilityName: String targetFacilityName: String
): String = { ): String = {
s"@CTF_Warning_NoCarrier1Min^@$facilityName~^@${GetFactionString(playerFaction)}~^@$targetFacilityName~" s"@CTF_Warning_NoCarrier1Min^@$facilityName~^@${GetFactionString(playerFaction)}~^@$targetFacilityName~"
} }

View file

@ -62,14 +62,14 @@ class HackCaptureActor extends Actor {
// If the base has a socket, but no flag spawned it means the hacked base is neutral with no friendly neighbouring bases to deliver to, making it a timed hack. // If the base has a socket, but no flag spawned it means the hacked base is neutral with no friendly neighbouring bases to deliver to, making it a timed hack.
val building = terminal.Owner.asInstanceOf[Building] val building = terminal.Owner.asInstanceOf[Building]
building.GetFlag match { building.GetFlag match {
case Some(llu) if llu.LastCollectionTime == llu.InitialSpawnTime => case Some(llu) if llu.Destroyed =>
// LLU was never once collected. Send resecured notifications // LLU was destroyed while in the field. Send resecured notifications
terminal.Zone.LocalEvents ! CaptureFlagManager.Lost(llu, CaptureFlagLostReasonEnum.TimedOut) terminal.Zone.LocalEvents ! CaptureFlagManager.Lost(llu, CaptureFlagLostReasonEnum.FlagLost)
NotifyHackStateChange(terminal, isResecured = true) NotifyHackStateChange(terminal, isResecured = true)
case Some(llu) => case Some(llu) =>
// LLU was not delivered in time. Send resecured notifications // LLU was not delivered in time. Send resecured notifications
terminal.Zone.LocalEvents ! CaptureFlagManager.Lost(llu, CaptureFlagLostReasonEnum.FlagLost) terminal.Zone.LocalEvents ! CaptureFlagManager.Lost(llu, CaptureFlagLostReasonEnum.TimedOut)
NotifyHackStateChange(terminal, isResecured = true) NotifyHackStateChange(terminal, isResecured = true)
case _ => case _ =>