From e4275cf298670cab3986bb1e63859c5f360b8f76 Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Mon, 19 Aug 2024 19:35:08 -0400 Subject: [PATCH] periodic message while the llu is active, whether or not there is a carrier, until the hack is finished --- .../local/support/CaptureFlagManager.scala | 180 +++++++++++------- .../local/support/HackCaptureActor.scala | 8 +- 2 files changed, 115 insertions(+), 73 deletions(-) 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 46127229c..c451ce2b9 100644 --- a/src/main/scala/net/psforever/services/local/support/CaptureFlagManager.scala +++ b/src/main/scala/net/psforever/services/local/support/CaptureFlagManager.scala @@ -22,12 +22,13 @@ import scala.concurrent.duration.DurationInt * Responsible for handling capture flag related lifecycles */ class CaptureFlagManager(zone: Zone) extends Actor { + import CaptureFlagManager.CaptureFlagEntry private[this] val log = org.log4s.getLogger(self.path.name) private var galaxyService: ActorRef = ActorRef.noSender private var mapUpdateTick: Cancellable = Default.Cancellable /** 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") @@ -39,7 +40,6 @@ class CaptureFlagManager(zone: Zone) extends Actor { DoMapUpdate() case CaptureFlagManager.SpawnCaptureFlag(capture_terminal, target, hackingFaction) => - val zone = capture_terminal.Zone val socket = capture_terminal.Owner.asInstanceOf[Building].GetFlagSocket.get // Override CC message when looked at zone.LocalEvents ! LocalServiceMessage( @@ -62,16 +62,16 @@ class CaptureFlagManager(zone: Zone) extends Actor { socket.captureFlag = flag TrackFlag(flag) TaskWorkflow.execute(WorldSession.CallBackForTask( - GUIDTask.registerObject(socket.Zone.GUID, flag), - socket.Zone.LocalEvents, + GUIDTask.registerObject(zone.GUID, flag), + zone.LocalEvents, LocalServiceMessage( - socket.Zone.id, + zone.id, LocalAction.LluSpawned(Service.defaultPlayerGUID, flag) ) )) // Broadcast chat message for LLU spawn 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) => val name = flag.Carrier match { @@ -79,29 +79,29 @@ class CaptureFlagManager(zone: Zone) extends Actor { case None => "A soldier" } // 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 - 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 HandleFlagDespawn(flag) case CaptureFlagManager.Lost(flag: CaptureFlag, reason: CaptureFlagLostReasonEnum) => reason match { case CaptureFlagLostReasonEnum.Resecured => - ChatBroadcast( - flag.Zone, + CaptureFlagManager.ChatBroadcast( + zone, CaptureFlagChatMessageStrings.CTF_Failed_SourceResecured(flag.Owner.asInstanceOf[Building].Name, flag.Faction) ) case CaptureFlagLostReasonEnum.TimedOut => val building = flag.Owner.asInstanceOf[Building] - ChatBroadcast( - flag.Zone, + CaptureFlagManager.ChatBroadcast( + zone, CaptureFlagChatMessageStrings.CTF_Failed_TimedOut(building.Name, flag.Target.Name, flag.Faction) ) case CaptureFlagLostReasonEnum.FlagLost => val building = flag.Owner.asInstanceOf[Building] - ChatBroadcast( - flag.Zone, + CaptureFlagManager.ChatBroadcast( + zone, CaptureFlagChatMessageStrings.CTF_Failed_FlagLost(building.Name, flag.Faction) ) case CaptureFlagLostReasonEnum.Ended => @@ -110,11 +110,14 @@ class CaptureFlagManager(zone: Zone) extends Actor { HandleFlagDespawn(flag) case CaptureFlagManager.PickupFlag(flag: CaptureFlag, player: Player) => - val continent = flag.Zone flag.Carrier = Some(player) - continent.LocalEvents ! LocalServiceMessage(continent.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)) - ChatBroadcast(flag.Zone, CaptureFlagChatMessageStrings.CTF_FlagPickedUp(player.Name, player.Faction, flag.Owner.asInstanceOf[Building].Name), fanfare = false) + zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.SendPacket(ObjectAttachMessage(player.GUID, flag.GUID, 252))) + zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.TriggerSound(PlanetSideGUID(-1), TriggeredSound.LLUPickup, player.Position, 15, volume = 0.8f)) + CaptureFlagManager.ChatBroadcast( + zone, + CaptureFlagChatMessageStrings.CTF_FlagPickedUp(player.Name, player.Faction, flag.Owner.asInstanceOf[Building].Name), + fanfare = false + ) case CaptureFlagManager.DropFlag(flag: CaptureFlag) => flag.Carrier match { @@ -125,9 +128,13 @@ class CaptureFlagManager(zone: Zone) extends Actor { // Remove attached player from flag flag.Carrier = None // 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 - 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) // Register LLU object create task and callback to create on clients val replacementLlu = CaptureFlag.Constructor( @@ -142,10 +149,10 @@ class CaptureFlagManager(zone: Zone) extends Actor { socket.captureFlag = replacementLlu TrackFlag(replacementLlu) TaskWorkflow.execute(WorldSession.CallBackForTask( - GUIDTask.registerObject(socket.Zone.GUID, replacementLlu), - socket.Zone.LocalEvents, + GUIDTask.registerObject(zone.GUID, replacementLlu), + zone.LocalEvents, LocalServiceMessage( - socket.Zone.id, + zone.id, LocalAction.LluSpawned(Service.defaultPlayerGUID, replacementLlu) ) )) @@ -158,33 +165,47 @@ class CaptureFlagManager(zone: Zone) extends Actor { } 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( u1 = 0, - owner_map_id = flag.Owner.asInstanceOf[Building].MapId, + owner_map_id = owner.MapId, target_map_id = flag.Target.MapId, - x = flag.Position.x, - y = flag.Position.y, - hack_time_remaining = flag.Owner.asInstanceOf[Building].infoUpdateMessage().hack_time_remaining, + x = pos.x, + y = pos.y, + hack_time_remaining = hackTimeRemaining, is_monolith_unit = false ) - ) + } galaxyService ! GalaxyServiceMessage(GalaxyAction.FlagMapUpdate(CaptureFlagUpdateMessage(zone.Number, flagInfo))) } private def TrackFlag(flag: CaptureFlag): Unit = { flag.Owner.Amenities = flag - flags = flags :+ flag + flags = flags :+ CaptureFlagEntry(flag) if (mapUpdateTick.isCancelled) { // Start sending map updates periodically 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 = { flag.Owner.RemoveAmenity(flag) - flags = flags.filterNot(x => x eq flag) + flags = flags.filterNot(x => x.flag eq flag) if (flags.isEmpty) { mapUpdateTick.cancel() DoMapUpdate() @@ -192,7 +213,6 @@ class CaptureFlagManager(zone: Zone) extends Actor { } private def HandleFlagDespawn(flag: CaptureFlag): Unit = { - val zone = flag.Zone // Remove the flag as an amenity flag.Owner.asInstanceOf[Building].GetFlagSocket.get.captureFlag = None UntrackFlag(flag) @@ -201,6 +221,23 @@ class CaptureFlagManager(zone: Zone) extends Actor { // Then unregister it from the GUID pool 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 = { //todo use UNK_222 sometimes @@ -217,17 +254,22 @@ class CaptureFlagManager(zone: Zone) extends Actor { ) ) } -} -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 def ComposeWarningMessage(flag: CaptureFlag, buildingName: String, minutesLeft: Int): String = { + import CaptureFlagChatMessageStrings._ + val carrier = flag.Carrier + val hasCarrier = carrier.nonEmpty + minutesLeft match { + case 1 if hasCarrier => + CTF_Warning_Carrier1Min(carrier.get.Name, flag.Faction, buildingName, flag.Target.Name) + case 1 => + 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 { @@ -263,55 +305,55 @@ object CaptureFlagChatMessageStrings { // @CTF_FlagPickedUp=%1 of the %2 picked up %3'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~" // @CTF_FlagDropped=%1 of the %2 dropped %3'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~" // @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! */ - def CTF_Warning_Carrier( - playerName:String, - playerFaction: PlanetSideEmpire.Value, - facilityName: String, - targetFacilityName: String, - time: Int - ): String = { + private[support] def CTF_Warning_Carrier( + playerName:String, + playerFaction: PlanetSideEmpire.Value, + facilityName: String, + targetFacilityName: String, + time: Int + ): String = { 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! /** {playerName} of the {faction} has {facilityName}'s LLU.\nIt must be taken to {targetFacilityName} within the next minute! */ - def CTF_Warning_Carrier1Min( - playerName:String, - playerFaction: PlanetSideEmpire.Value, - facilityName: String, - targetFacilityName: String - ): String = { + private[support] def CTF_Warning_Carrier1Min( + playerName:String, + playerFaction: PlanetSideEmpire.Value, + facilityName: String, + targetFacilityName: String + ): String = { 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! /** {facilityName}'s LLU is in the field.\nThe {faction} must take it to {targetFacilityName} within {time} minute! */ - def CTF_Warning_NoCarrier( - facilityName: String, - playerFaction: PlanetSideEmpire.Value, - targetFacilityName: String, - time: Int - ): String = { + private[support] def CTF_Warning_NoCarrier( + facilityName: String, + playerFaction: PlanetSideEmpire.Value, + targetFacilityName: String, + time: Int + ): String = { 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! /** {facilityName}'s LLU is in the field.\nThe {faction} must take it to {targetFacilityName} within the next minute! */ - def CTF_Warning_NoCarrier1Min( - facilityName: String, - playerFaction: PlanetSideEmpire.Value, - targetFacilityName: String - ): String = { + private[support] def CTF_Warning_NoCarrier1Min( + facilityName: String, + playerFaction: PlanetSideEmpire.Value, + targetFacilityName: String + ): String = { s"@CTF_Warning_NoCarrier1Min^@$facilityName~^@${GetFactionString(playerFaction)}~^@$targetFacilityName~" } 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 2423677d1..154c0c7c7 100644 --- a/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala +++ b/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala @@ -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. val building = terminal.Owner.asInstanceOf[Building] building.GetFlag match { - case Some(llu) if llu.LastCollectionTime == llu.InitialSpawnTime => - // LLU was never once collected. Send resecured notifications - terminal.Zone.LocalEvents ! CaptureFlagManager.Lost(llu, CaptureFlagLostReasonEnum.TimedOut) + case Some(llu) if llu.Destroyed => + // LLU was destroyed while in the field. Send resecured notifications + terminal.Zone.LocalEvents ! CaptureFlagManager.Lost(llu, CaptureFlagLostReasonEnum.FlagLost) NotifyHackStateChange(terminal, isResecured = true) case Some(llu) => // 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) case _ =>