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
*/
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~"
}

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.
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 _ =>