allowed bundling of event message envelopes to streamline event system communication

This commit is contained in:
Fate-JH 2026-06-02 18:19:52 -04:00
parent f1b0d2e920
commit f3fedaf55d
15 changed files with 256 additions and 145 deletions

View file

@ -11,7 +11,7 @@ import net.psforever.objects.zones.Zone
import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.ContinentalLockUpdateMessage
import net.psforever.persistence
import net.psforever.services.base.envelope.MessageEnvelope
import net.psforever.services.base.envelope.{BundledEnvelope, MessageEnvelope}
import net.psforever.services.base.message.{SendResponse, SetEmpire}
import net.psforever.services.galaxy.GalaxyAction
import net.psforever.services.{InterstellarClusterService, ServiceManager}
@ -232,8 +232,10 @@ class BuildingActor(
Behaviors.same
case MapUpdate() =>
details.galaxyService ! MessageEnvelope("", GalaxyAction.MapUpdate(details.building.infoUpdateMessage()))
details.galaxyService ! MessageEnvelope("", SendResponse(details.building.densityLevelUpdateMessage(building)))
details.galaxyService ! BundledEnvelope(
MessageEnvelope("", GalaxyAction.MapUpdate(details.building.infoUpdateMessage())),
MessageEnvelope("", SendResponse(details.building.densityLevelUpdateMessage(building)))
)
Behaviors.same
case AmenityStateChange(amenity, data) =>

View file

@ -15,7 +15,7 @@ import net.psforever.objects.serverobject.terminals.capture.{CaptureTerminal, Ca
import net.psforever.objects.sourcing.PlayerSource
import net.psforever.packet.game.{GenericObjectActionMessage, PlanetsideAttributeMessage}
import net.psforever.services.InterstellarClusterService
import net.psforever.services.base.envelope.MessageEnvelope
import net.psforever.services.base.envelope.{BundledEnvelope, MessageEnvelope}
import net.psforever.services.base.message.{GenericObjectAction, PlanetsideAttribute, SendResponse}
import net.psforever.services.galaxy.GalaxyAction
import net.psforever.services.local.support.{CaptureEnvelope, HackCaptureActor, HackClearActor, HackClearEnvelope}
@ -204,25 +204,19 @@ case object MajorFacilityLogic
val events = zone.AvatarEvents
val guid = building.GUID
val msg = GenericObjectAction(guid, 15)
building.PlayersInSOI.foreach { player =>
events ! MessageEnvelope(player.Name, msg)
}
events ! BundledEnvelope(building.PlayersInSOI.map { player => MessageEnvelope(player.Name, msg) })
false
case Some(GeneratorControl.Event.Critical) =>
val events = zone.AvatarEvents
val guid = building.GUID
val msg = PlanetsideAttribute(guid, 46, 1)
building.PlayersInSOI.foreach { player =>
events ! MessageEnvelope(player.Name, msg)
}
events ! BundledEnvelope(building.PlayersInSOI.map { player => MessageEnvelope(player.Name, msg) })
true
case Some(GeneratorControl.Event.Destabilized) =>
val events = zone.AvatarEvents
val guid = building.GUID
val msg = GenericObjectAction(guid, 16)
building.PlayersInSOI.foreach { player =>
events ! MessageEnvelope(player.Name, msg)
}
events ! BundledEnvelope(building.PlayersInSOI.map { player => MessageEnvelope(player.Name, msg) })
if (building.hasCavernLockBenefit) {
zone.LocalEvents ! MessageEnvelope(
zone.id,
@ -235,10 +229,9 @@ case object MajorFacilityLogic
case Some(GeneratorControl.Event.Offline) =>
powerLost(details)
val zone = building.Zone
val events = zone.AvatarEvents
val msg = PlanetsideAttribute(building.GUID, 46, 2)
building.PlayersInSOI.foreach { player =>
zone.AvatarEvents ! MessageEnvelope(player.Name, msg)
} //???
events ! BundledEnvelope(building.PlayersInSOI.map { player => MessageEnvelope(player.Name, msg) }) //???
true
case Some(GeneratorControl.Event.Normal) =>
true
@ -253,9 +246,7 @@ case object MajorFacilityLogic
PlanetsideAttributeMessage(guid, 46, 0),
GenericObjectActionMessage(guid, 17)
))
building.PlayersInSOI.foreach { player =>
events ! MessageEnvelope(player.Name, list)
}
events ! BundledEnvelope(building.PlayersInSOI.map { player => MessageEnvelope(player.Name, list) })
true
case _ =>
false

View file

@ -6,7 +6,7 @@ import akka.actor.typed.scaladsl.Behaviors
import net.psforever.actors.commands.NtuCommand
import net.psforever.actors.zone.BuildingActor
import net.psforever.objects.serverobject.structures.{Amenity, Building, WarpGate}
import net.psforever.services.base.envelope.MessageEnvelope
import net.psforever.services.base.envelope.{BundledEnvelope, MessageEnvelope}
import net.psforever.services.galaxy.GalaxyAction
import net.psforever.types.PlanetSideEmpire
import net.psforever.util.Config
@ -209,9 +209,7 @@ case object WarpGateLogic
warpgate.Zone.Number, warpgate.MapId, previousAllowances, setBroadcastTo
)
warpgate.AllowBroadcastFor = setBroadcastTo
(setBroadcastTo ++ previousAllowances).foreach { faction =>
events ! MessageEnvelope(faction.toString, msg)
}
events ! BundledEnvelope((setBroadcastTo ++ previousAllowances).map { faction => MessageEnvelope(faction.toString, msg) })
}
/**

View file

@ -107,10 +107,7 @@ class BoomerDeployableControl(mine: BoomerDeployable)
zone.Ground ! Zone.Ground.RemoveItem(guid)
case _ => ()
}
zone.AvatarEvents! MessageEnvelope(
zone.id,
ObjectDelete(guid)
)
zone.AvatarEvents! MessageEnvelope(zone.id, ObjectDelete(guid))
TaskWorkflow.execute(GUIDTask.unregisterObject(zone.GUID, trigger))
case None => ()
}

View file

@ -20,11 +20,12 @@ import net.psforever.objects.zones.Zone
import net.psforever.packet.game._
import net.psforever.types.{ChatMessageType, ExoSuitType, PlanetSideGUID, Vector3}
import net.psforever.services.avatar.AvatarAction
import net.psforever.services.base.envelope.MessageEnvelope
import net.psforever.services.base.envelope.{BundledEnvelope, MessageEnvelope}
import net.psforever.services.base.message.{ObjectDelete, SendResponse}
import net.psforever.services.local.LocalAction
import scala.annotation.tailrec
import scala.collection.mutable.ArrayBuffer
object Players {
private val log = org.log4s.getLogger("Players")
@ -49,10 +50,7 @@ object Players {
) {
val events = target.Zone.AvatarEvents
val uname = user.Name
events ! MessageEnvelope(
uname,
SendResponse(RepairMessage(target.GUID, progress.toInt))
)
events ! MessageEnvelope(uname, SendResponse(RepairMessage(target.GUID, progress.toInt)))
true
} else {
false
@ -439,24 +437,20 @@ object Players {
if ((player.Slot(index).Equipment = obj).contains(obj)) {
val fireMode = tool.FireModeIndex
val ammoType = tool.AmmoTypeIndex
val list: ArrayBuffer[MessageEnvelope] = ArrayBuffer()
player.Inventory -= x.start
obj.FireModeIndex = fireMode
//TODO any penalty for being handed an OCM version of the tool?
events ! MessageEnvelope(
zone.id,
AvatarAction.EquipmentInHand(pguid, index, obj)
)
list.append(MessageEnvelope(zone.id, AvatarAction.EquipmentInHand(pguid, index, obj)))
if (obj.AmmoTypeIndex != ammoType) {
obj.AmmoTypeIndex = ammoType
events ! MessageEnvelope(
name,
SendResponse(ChangeAmmoMessage(obj.GUID, ammoType))
)
list.append(MessageEnvelope(name, SendResponse(ChangeAmmoMessage(obj.GUID, ammoType))))
}
if (player.DrawnSlot == Player.HandsDownSlot) {
player.DrawnSlot = index
events ! MessageEnvelope(zone.id, pguid, AvatarAction.ObjectHeld(index, index))
list.append(MessageEnvelope(zone.id, pguid, AvatarAction.ObjectHeld(index, index)))
}
events ! BundledEnvelope(list)
}
case Nil => ; //no replacements found
}

View file

@ -12,11 +12,13 @@ import net.psforever.objects.vehicles._
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{ChatMsg, FrameVehicleStateMessage, GenericObjectActionEnum, HackMessage, HackState, HackState1, HackState7, TriggeredSound, VehicleStateMessage}
import net.psforever.types.{ChatMessageType, DriveState, PlanetSideEmpire, PlanetSideGUID, Vector3}
import net.psforever.services.base.envelope.MessageEnvelope
import net.psforever.services.base.envelope.{BundledEnvelope, MessageEnvelope}
import net.psforever.services.base.message.{GenericObjectAction, PlanetsideAttribute, SendResponse, SetEmpire}
import net.psforever.services.local.LocalAction
import net.psforever.services.vehicle.VehicleAction
import scala.collection.mutable.ArrayBuffer
//import scala.concurrent.duration._
object Vehicles {
@ -248,13 +250,15 @@ object Vehicles {
val hFaction = hacker.Faction
val zone = target.Zone
val zoneid = zone.id
val avatarEvents = zone.AvatarEvents
val vehicleEvents = zone.VehicleEvents
val localEvents = zone.LocalEvents
val previousOwnerName = target.OwnerName.getOrElse("")
vehicleEvents ! MessageEnvelope(
val occupantMessages: ArrayBuffer[MessageEnvelope] = ArrayBuffer()
val vehicleMessages: ArrayBuffer[MessageEnvelope] = ArrayBuffer()
vehicleMessages.append(MessageEnvelope(
zoneid,
SendResponse(HackMessage(HackState1.Unk2, tGuid, hGuid, 100, 0f, HackState.Hacked, HackState7.Unk8))
)
))
target.Actor ! CommonMessages.Hack(hacker, target)
// Forcefully dismount any cargo
target.CargoHolds.foreach { case (_, cargoHold) =>
@ -268,26 +272,29 @@ object Vehicles {
player: Player =>
seat.unmount(player)
player.VehicleSeated = None
vehicleEvents ! MessageEnvelope(
occupantMessages.append(MessageEnvelope(
zoneid,
player.GUID,
VehicleAction.KickPassenger(4, unk2 = false, tGuid)
)
))
}
// In case BFR is occupied and may or may not be crouched
if (GlobalDefinitions.isBattleFrameVehicle(target.Definition) && target.Seat(0).isDefined) {
zone.LocalEvents ! MessageEnvelope(
zoneid,
GenericObjectAction(target.GUID, GenericObjectActionEnum.BFRShieldsDown.id)
)
zone.LocalEvents ! MessageEnvelope(
zoneid,
SendResponse(
FrameVehicleStateMessage(target.GUID, 0, target.Position, target.Orientation, Some(Vector3(0f, 0f, 0f)), unk2=false, 0, 0, is_crouched=true, is_airborne=false, ascending_flight=false, 10, 0, 0)))
zone.LocalEvents ! MessageEnvelope(
zoneid,
SendResponse(
VehicleStateMessage(target.GUID, 0, target.Position, target.Orientation, Some(Vector3(0f, 0f, 0f)), None, 0, 0, 15, is_decelerating=false, is_cloaked=false)))
vehicleMessages.appendAll(List(
MessageEnvelope(zoneid,
GenericObjectAction(target.GUID, GenericObjectActionEnum.BFRShieldsDown.id)
),
MessageEnvelope(zoneid,
SendResponse(
FrameVehicleStateMessage(target.GUID, 0, target.Position, target.Orientation, Some(Vector3(0f, 0f, 0f)), unk2=false, 0, 0, is_crouched=true, is_airborne=false, ascending_flight=false, 10, 0, 0)
)
),
MessageEnvelope(zoneid,
SendResponse(
VehicleStateMessage(target.GUID, 0, target.Position, target.Orientation, Some(Vector3(0f, 0f, 0f)), None, 0, 0, 15, is_decelerating=false, is_cloaked=false)
)
)
))
}
})
// If the vehicle can fly and is flying: deconstruct it; and well played to whomever managed to hack a plane in mid air
@ -312,26 +319,17 @@ object Vehicles {
Vehicles.Own(target, hacker)
//todo: Send HackMessage -> HackCleared to vehicle? can be found in packet captures. Not sure if necessary.
// And broadcast the faction change to other clients
zone.AvatarEvents ! MessageEnvelope(
zoneid,
SetEmpire(tGuid, hFaction)
)
vehicleMessages.append(MessageEnvelope(zoneid, SetEmpire(tGuid, hFaction)))
}
localEvents ! MessageEnvelope(
vehicleMessages.append(MessageEnvelope(
zoneid,
hGuid,
LocalAction.TriggerSound(TriggeredSound.HackVehicle, target.Position, 30, 0.49803925f)
)
))
if (zone.Players.exists(_.name.equals(previousOwnerName))) {
localEvents ! MessageEnvelope(
previousOwnerName,
SendResponse(ChatMsg(ChatMessageType.UNK_226, "@JackStolen"))
)
vehicleMessages.append(MessageEnvelope(previousOwnerName, SendResponse(ChatMsg(ChatMessageType.UNK_226, "@JackStolen"))))
}
localEvents ! MessageEnvelope(
hacker.Name,
SendResponse(ChatMsg(ChatMessageType.UNK_226, "@JackVehicleOwned"))
)
occupantMessages.append(MessageEnvelope(hacker.Name, SendResponse(ChatMsg(ChatMessageType.UNK_226, "@JackVehicleOwned"))))
// Clean up after specific vehicles, e.g. remove router telepads
// If AMS is deployed, swap it to the new faction
target.Definition match {
@ -343,13 +341,15 @@ object Vehicles {
util.Actor ! TelepadLike.Activate(util)
}
case GlobalDefinitions.ams if target.DeploymentState == DriveState.Deployed =>
vehicleEvents ! MessageEnvelope(zone.id, VehicleAction.AMSDeploymentChange(zone))
vehicleMessages.append(MessageEnvelope(zone.id, VehicleAction.AMSDeploymentChange(zone)))
case _ => ()
}
vehicleEvents ! MessageEnvelope(
vehicleMessages.append(MessageEnvelope(
zoneid,
SendResponse(HackMessage(HackState1.Unk2, tGuid, tGuid, 0, 1L, HackState.HackCleared, HackState7.Unk8))
)
))
avatarEvents ! BundledEnvelope(occupantMessages)
vehicleEvents ! BundledEnvelope(vehicleMessages)
target.Actor ! CommonMessages.ClearHack()
}

View file

@ -6,7 +6,7 @@ import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
import net.psforever.objects._
import net.psforever.objects.zones.Zone
import net.psforever.packet.game._
import net.psforever.services.base.envelope.MessageEnvelope
import net.psforever.services.base.envelope.{BundledEnvelope, MessageEnvelope}
import net.psforever.services.base.message.SetEmpire
import net.psforever.services.local.LocalAction
import net.psforever.types.PlanetSideEmpire
@ -198,11 +198,14 @@ trait DeployableBehavior {
None
}
//zone build
localEvents ! MessageEnvelope(zone.id, LocalAction.DeployItem(obj))
//zone map icon
localEvents ! MessageEnvelope(
obj.Faction.toString,
LocalAction.DeployableMapIcon(DeploymentAction.Build, DeployableInfo(obj.GUID, Deployable.Icon(obj.Definition.Item), obj.Position, obj.OwnerGuid.getOrElse(Default.GUID0)))
localEvents ! BundledEnvelope(
/* zone build */
MessageEnvelope(zone.id, LocalAction.DeployItem(obj)),
/* zone map icon */
MessageEnvelope(
obj.Faction.toString,
LocalAction.DeployableMapIcon(DeploymentAction.Build, DeployableInfo(obj.GUID, Deployable.Icon(obj.Definition.Item), obj.Position, obj.OwnerGuid.getOrElse(Default.GUID0)))
)
)
//local build management
callback ! Zone.Deployable.IsBuilt(obj)
@ -281,15 +284,13 @@ object DeployableBehavior {
val localEvents = zone.LocalEvents
if (originalFaction != toFaction) {
obj.Faction = toFaction
//visual tells in regards to ownership by faction
zone.AvatarEvents ! MessageEnvelope(
zone.id,
SetEmpire(dGuid, toFaction)
)
//remove knowledge by the previous owner's faction
localEvents ! MessageEnvelope(
originalFaction.toString,
LocalAction.DeployableMapIcon(DeploymentAction.Dismiss, info)
localEvents ! BundledEnvelope(
/* visual tells in regards to ownership by faction */
MessageEnvelope(zone.id, SetEmpire(dGuid, toFaction)),
/* remove knowledge by the previous owner's faction */
MessageEnvelope(originalFaction.toString, LocalAction.DeployableMapIcon(DeploymentAction.Dismiss, info)),
/* display to the given faction */
MessageEnvelope(toFaction.toString, LocalAction.DeployableMapIcon(DeploymentAction.Build, info))
)
//remove deployable from original owner's toolbox and UI counter
zone.AllPlayers.filter(p => obj.OriginalOwnerName.contains(p.Name))
@ -297,11 +298,6 @@ object DeployableBehavior {
originalOwner.avatar.deployables.Remove(obj)
originalOwner.Zone.LocalEvents ! MessageEnvelope(originalOwner.Name, LocalAction.DeployableUIFor(obj.Definition.Item))
}
//display to the given faction
localEvents ! MessageEnvelope(
toFaction.toString,
LocalAction.DeployableMapIcon(DeploymentAction.Build, info)
)
}
}
}

View file

@ -9,7 +9,7 @@ import net.psforever.objects.vehicles.Utility.InternalTelepad
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{GenericObjectActionMessage, ObjectCreateMessage, ObjectDeleteMessage}
import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
import net.psforever.services.base.envelope.MessageEnvelope
import net.psforever.services.base.envelope.{BundledEnvelope, MessageEnvelope}
import net.psforever.services.base.message.SendResponse
import net.psforever.services.local.LocalAction
import net.psforever.types.PlanetSideGUID
@ -115,21 +115,23 @@ object TelepadLike {
normally dispatched while the Router is transitioned into its Deploying state
it is safe, however, to perform these actions at any time during and after the Deploying state
*/
events ! MessageEnvelope(
zoneId,
SendResponse(
ObjectCreateMessage(
udef.ObjectId,
utilityGUID,
ObjectCreateMessageParent(routerGUID, 2), //TODO stop assuming slot number
udef.Packet.ConstructorData(obj).get
events ! BundledEnvelope(
MessageEnvelope(
zoneId,
SendResponse(
ObjectCreateMessage(
udef.ObjectId,
utilityGUID,
ObjectCreateMessageParent(routerGUID, 2), //TODO stop assuming slot number
udef.Packet.ConstructorData(obj).get
)
)
)
),
MessageEnvelope(zoneId, SendResponse(Seq(
GenericObjectActionMessage(utilityGUID, 27),
GenericObjectActionMessage(utilityGUID, 30)
)))
)
events ! MessageEnvelope(zoneId, SendResponse(Seq(
GenericObjectActionMessage(utilityGUID, 27),
GenericObjectActionMessage(utilityGUID, 30)
)))
LinkTelepad(zone, utilityGUID)
}

View file

@ -7,7 +7,7 @@ import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
import net.psforever.objects.vital.interaction.{Adversarial, DamageResult}
import net.psforever.objects.vital.{DamagingActivity, HealingActivity, InGameActivity, RepairingActivity, RevivingActivity, SpawningActivity}
import net.psforever.services.avatar.AvatarAction
import net.psforever.services.base.envelope.MessageEnvelope
import net.psforever.services.base.envelope.{BundledEnvelope, MessageEnvelope}
import net.psforever.types.PlanetSideEmpire
import net.psforever.util.Config
@ -52,9 +52,9 @@ object KillAssists {
history: Iterable[InGameActivity],
eventBus: ActorRef
): Unit = {
rewardThisPlayerDeath(victim, lastDamage, history).foreach { case (p, kda) =>
eventBus ! MessageEnvelope(p.Name, AvatarAction.UpdateKillsDeathsAssists(p.CharId, kda))
}
eventBus ! BundledEnvelope(rewardThisPlayerDeath(victim, lastDamage, history).map { case (p, kda) =>
MessageEnvelope(p.Name, AvatarAction.UpdateKillsDeathsAssists(p.CharId, kda))
})
}
/**

View file

@ -4,7 +4,7 @@ package net.psforever.services.base
import akka.actor.Actor
import net.psforever.services.Service
import net.psforever.services.base.bus.GenericEventBus
import net.psforever.services.base.envelope.GenericMessageEnvelope
import net.psforever.services.base.envelope.{BundledEnvelope, GenericMessageEnvelope}
import org.log4s.Logger
/**
@ -78,6 +78,9 @@ class GenericEventService(stamp: EventSystemStamp)
* Accept and handle designated messages.
*/
protected def commonBehavior: Receive = {
case bundle: BundledEnvelope =>
bundle.msgs.foreach(commonBehavior.apply)
case msg: GenericMessageEnvelope =>
handleMessage(msg)
}

View file

@ -3,7 +3,7 @@ package net.psforever.services.base
import akka.actor.Cancellable
import net.psforever.objects.Default
import net.psforever.services.base.envelope.{GenericMessageEnvelope, GenericResponseEnvelope, MessageEnvelope, MessageTransformationBehavior}
import net.psforever.services.base.envelope.{BundledEnvelope, GenericMessageEnvelope, GenericResponseEnvelope, MessageEnvelope, MessageTransformationBehavior}
import net.psforever.services.base.message.EventMessage
import net.psforever.types.PlanetSideGUID
import net.psforever.util.Config
@ -137,18 +137,35 @@ class GenericEventServiceWithCacheAndSupport
}
/**
* Add messages to the cache based on their channel, then their type, then their cache target identifier.
* Add messages to the cache.
* @param event event system message
*/
private def pushToCache(event: CachedGenericEventEnvelope): Unit = {
pushToCache(event, event.msg.getClass.getName, event.guid)
}
/**
* Add messages to the cache.
* @param event event system message
* @param eventGuid event system message filter
*/
private def pushToCache(event: GenericMessageEnvelope, eventGuid: PlanetSideGUID): Unit = {
pushToCache(event, event.msg.getClass.getName, eventGuid)
}
/**
* Add messages to the cache
* based on their channel, then their type, then their cache target identifier.
* Messages that arrive with the same cache profile as a previous message,
* but before that previous message was dispatched,
* will overwrite the previous message without fanfare or warning.
* @param event event system message
* @param eventClassName event system message identifier
* @param eventGuid event system message filter
*/
private def pushToCache(event: CachedGenericEventEnvelope): Unit = {
val eventClassName = event.msg.getClass.getName
private def pushToCache(event: GenericMessageEnvelope, eventClassName: String, eventGuid: PlanetSideGUID): Unit = {
val updateBranch = cache
.getOrElseUpdate(event.channel, new ConcurrentHashMap[String, CMap[PlanetSideGUID, GenericMessageEnvelope]]().asScala)
.getOrElseUpdate(eventClassName, new ConcurrentHashMap[PlanetSideGUID, GenericMessageEnvelope]().asScala)
updateBranch.updateWith(event.guid) { _ => Some(event) }
updateBranch.updateWith(eventGuid) { _ => Some(event) }
tryRetimeFlushCache()
}
@ -194,6 +211,8 @@ class GenericEventServiceWithCacheAndSupport
event match {
case FlushCachedMessages =>
flushCache()
case bundle: BundledEnvelope =>
handleMessageBundled(bundle)
case _: CachedGenericEventEnvelope if tryFlushCache() =>
super.handleMessage(event)
case envelope: CachedGenericEventEnvelope =>
@ -203,4 +222,24 @@ class GenericEventServiceWithCacheAndSupport
super.handleMessage(event)
}
}
/**
* If even one message in a bundle is to be cached, the contents of the whole bundle should be cached.
* Otherwise, just handle things normally.
* @param bundle event system message that may be cached
*/
private def handleMessageBundled(bundle: BundledEnvelope): Unit = {
val messages = bundle.msgs
messages.find(_.isInstanceOf[CachedGenericEventEnvelope]) match {
case Some(cache: CachedGenericEventEnvelope) if messages.size == 1 =>
pushToCache(messages.head, cache.guid)
tryFlushCache()
case Some(cache: CachedGenericEventEnvelope) =>
val guid = cache.guid
messages.foreach(msg => pushToCache(msg, guid))
tryFlushCache()
case _ =>
messages.foreach(handleMessage)
}
}
}

View file

@ -0,0 +1,83 @@
// Copyright (c) 2026 PSForever
package net.psforever.services.base.envelope
import net.psforever.objects.Default
import net.psforever.services.base.EventSystemStamp
import net.psforever.services.base.message.{EventMessage, EventResponse}
import net.psforever.types.PlanetSideGUID
import scala.annotation.tailrec
/**
* A message when there should be none, but a message is required to be defined anyway.
*/
case object NoMessage extends EventMessage {
override def response(): EventResponse = NoReply
}
/**
* A response envelope when there should be none, but an envelope for messaging product is required to be defined anyway.
*/
case object NoResponse extends GenericResponseEnvelope {
override def reply: EventResponse = NoReply
override def stamp: EventSystemStamp = Undelivered
override def channel: String = ""
override def filter: PlanetSideGUID = Default.GUID0
}
/**
* A collection of messaging envelopes to be dispatched to an event system at the same time
* within the conditions of event system synchronization between different messages.
* All messages contained within the bundling are to be processed at the time of processing by the bundling.
* The order of the bundled message envelopes should be considered important.
* @see `SendResponse(Seq[PlanetSideGamePacket])`
* @param msgs list of message envelopes
*/
final case class BundledEnvelope(msgs: Iterable[GenericMessageEnvelope])
extends GenericMessageEnvelope {
assert(msgs.size == BundledEnvelope.unwind(msgs).size, "do not nest bundled event system envelopes")
override def msg: EventMessage = NoMessage
override def response(stamp: EventSystemStamp): GenericResponseEnvelope = NoResponse
override def channel: String = ""
override def filter: PlanetSideGUID = Default.GUID0
}
object BundledEnvelope {
/**
* Overloaded constructor for `BundledEnvelope` objects.
* The entities are separated between "the first" and "any others" to distinguish from
* the `case class` constructor that accepts any number of message envelopes
* including no message envelopes at all.
* @param first single, required `GenericMessageEnvelope` entity for bundling
* @param msgs any other `GenericMessageEnvelope` entity for bundling
* @return a `BundledEnvelope` object
*/
def apply(first: GenericMessageEnvelope, msgs: GenericMessageEnvelope*): BundledEnvelope = {
new BundledEnvelope(unwind(first +: msgs))
}
/**
* Input sanitization method that unpacks `BundledEnvelope` message envelopes
* producing a single-dimensional list of `GenericMessageEnvelope` entities.
* An assertion in the constructor of `BundledEnvelope` aborts object creation
* if a `BundledEnvelope` entity is within that `BundledEnvelope` entity.
* @param in list of `GenericMessageEnvelope` entities to be processed
* @param out list of `GenericMessageEnvelope` entities that have been processed
* @return list of `GenericMessageEnvelope` entities that have been processed
*/
@tailrec
private def unwind(in: Iterable[GenericMessageEnvelope], out: List[GenericMessageEnvelope] = Nil): List[GenericMessageEnvelope] = {
if (in.isEmpty) {
out
} else {
val first :: remainder = in
first match {
case bundle: BundledEnvelope =>
unwind(bundle.msgs ++ remainder, out)
case _ =>
unwind(remainder, out :+ first)
}
}
}
}

View file

@ -6,7 +6,7 @@ import net.psforever.objects.{Default, Doors}
import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.zones.Zone
import net.psforever.services.base.{EventServiceSupport, GenericSupportEnvelope}
import net.psforever.services.base.envelope.MessageEnvelope
import net.psforever.services.base.envelope.{BundledEnvelope, MessageEnvelope}
import net.psforever.services.local.LocalAction.IsADoorMessage
import net.psforever.services.local.LocalAction
import net.psforever.types.PlanetSideGUID
@ -64,11 +64,13 @@ class DoorCloseActor() extends Actor {
doorsLeftOpen1 ++
doorsLeftOpen2.map(entry => DoorCloseActor.DoorEntry(entry.door, entry.zone, now))
).sortBy(_.time)
doorsToClose2.foreach { case DoorCloseActor.DoorEntry(door, zone, _) =>
door.Open = None //permissible break from synchronization
zone.LocalEvents ! MessageEnvelope(zone.id, LocalAction.DoorCloses(door.GUID)) //call up to the main event system
}
doorsToClose2
.map { case DoorCloseActor.DoorEntry(door, zone, _) =>
door.Open = None //permissible break from synchronization
(zone, MessageEnvelope(zone.id, LocalAction.DoorCloses(door.GUID))) //call up to the main event system
}
.groupBy(_._1)
.foreach { case (zone, list) => zone.LocalEvents ! BundledEnvelope(list.map(_._2)) }
if (openDoors.nonEmpty) {
val short_timeout: FiniteDuration = math.max(1, DoorCloseActor.timeout_time - (now - openDoors.head.time)).milliseconds
import scala.concurrent.ExecutionContext.Implicits.global

View file

@ -14,7 +14,7 @@ import net.psforever.objects.serverobject.structures.participation.MajorFacility
import net.psforever.packet.game.{ChatMsg, GenericAction, HackState7, PlanetsideAttributeEnum}
import net.psforever.objects.sourcing.PlayerSource
import net.psforever.services.base.{EventServiceSupport, GenericSupportEnvelopeOnly}
import net.psforever.services.base.envelope.MessageEnvelope
import net.psforever.services.base.envelope.{BundledEnvelope, MessageEnvelope}
import net.psforever.services.base.message.PlanetsideAttribute
import net.psforever.services.local.support.HackCaptureActor.GetHackingFaction
import net.psforever.services.local.LocalAction
@ -22,6 +22,7 @@ import net.psforever.types.{ChatMessageType, PlanetSideEmpire, PlanetSideGUID}
import java.util.concurrent.{Executors, TimeUnit}
import scala.collection.Seq
import scala.collection.mutable.ArrayBuffer
import scala.concurrent.duration.{FiniteDuration, _}
import scala.util.Random
@ -277,18 +278,18 @@ class HackCaptureActor extends Actor {
private def HackCompleted(terminal: CaptureTerminal with Hackable, hackedByFaction: PlanetSideEmpire.Value): Unit = {
val building = terminal.Owner.asInstanceOf[Building]
val zone = building.Zone
val events = zone.LocalEvents
val messages: ArrayBuffer[MessageEnvelope] = ArrayBuffer()
if (building.NtuLevel > 0) {
building.virusId = 8
building.virusInstalledBy = None
log.info(s"Setting base ${building.GUID} / MapId: ${building.MapId} as owned by $hackedByFaction")
//dispatch to players aligned with the capturing faction within the SOI
val events = building.Zone.LocalEvents
val msg = LocalAction.GenericActionMessage(GenericAction.FacilityCaptureFanfare)
building
.PlayersInSOI
.collect { case p if p.Faction == hackedByFaction =>
events ! MessageEnvelope(p.Name, msg)
}
messages.appendAll(building.PlayersInSOI.collect { case p if p.Faction == hackedByFaction =>
MessageEnvelope(p.Name, msg)
})
val buildings = building.Zone.Buildings.values
val hackedBaseId = building.GUID
val facilities = if (building.Zone.id.startsWith("c")) {
@ -322,8 +323,8 @@ class HackCaptureActor extends Actor {
}
NotifyHackStateChange(terminal, isResecured = true)
// todo: this appears to be the way to reset the base warning lights after the hack finishes but it doesn't seem to work.
val zone = building.Zone
zone.LocalEvents ! MessageEnvelope(zone.id, LocalAction.HackClear(building.GUID, 3212836864L, HackState7.Unk8))
messages.append(MessageEnvelope(zone.id, LocalAction.HackClear(building.GUID, 3212836864L, HackState7.Unk8)))
events ! BundledEnvelope(messages)
}
private def RestartTimer(): Unit = {

View file

@ -8,7 +8,7 @@ import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.HackState7
import net.psforever.services.base.envelope.MessageEnvelope
import net.psforever.services.base.envelope.{BundledEnvelope, MessageEnvelope}
import net.psforever.services.base.{EventServiceSupport, GenericSupportEnvelope, GenericSupportEnvelopeOnly}
import net.psforever.services.base.message.GenericObjectAction
import net.psforever.services.local.LocalAction.IsAHackMessage
@ -72,13 +72,18 @@ class HackClearActor() extends Actor {
//TODO we can just walk across the list of doors and extract only the first few entries
val (unhackObjects, stillHackedObjects) = PartitionEntries(hackedObjects, now)
hackedObjects = stillHackedObjects
unhackObjects.foreach { case HackClearActor.HackEntry(target, zone, unk1, unk2, _, _) =>
target.Actor ! CommonMessages.ClearHack()
zone.LocalEvents ! MessageEnvelope(zone.id, LocalAction.HackClear(target.GUID, unk1, unk2))
if (target.Definition == GlobalDefinitions.main_terminal) {
ClearVirusFromBuilding(target)
unhackObjects
.map { case HackClearActor.HackEntry(target, zone, unk1, unk2, _, _) =>
target.Actor ! CommonMessages.ClearHack()
if (target.Definition == GlobalDefinitions.main_terminal) {
ClearVirusFromBuilding(target)
}
(zone, MessageEnvelope(zone.id, LocalAction.HackClear(target.GUID, unk1, unk2)))
}
.groupBy(_._1)
.foreach { case (zone, list) =>
zone.LocalEvents ! BundledEnvelope(list.map(_._2))
}
}
RestartTimer()
@ -132,9 +137,7 @@ class HackClearActor() extends Actor {
building.virusInstalledBy = None
val msg = GenericObjectAction(target.GUID, 60)
val events = building.Zone.AvatarEvents
building.PlayersInSOI.foreach { player =>
events ! MessageEnvelope(player.Name, msg)
}
events ! BundledEnvelope(building.PlayersInSOI.map { player => MessageEnvelope(player.Name, msg) })
building.Actor ! BuildingActor.MapUpdate()
}