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.PlanetSideGamePacket
import net.psforever.packet.game.ContinentalLockUpdateMessage import net.psforever.packet.game.ContinentalLockUpdateMessage
import net.psforever.persistence 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.base.message.{SendResponse, SetEmpire}
import net.psforever.services.galaxy.GalaxyAction import net.psforever.services.galaxy.GalaxyAction
import net.psforever.services.{InterstellarClusterService, ServiceManager} import net.psforever.services.{InterstellarClusterService, ServiceManager}
@ -232,8 +232,10 @@ class BuildingActor(
Behaviors.same Behaviors.same
case MapUpdate() => case MapUpdate() =>
details.galaxyService ! MessageEnvelope("", GalaxyAction.MapUpdate(details.building.infoUpdateMessage())) details.galaxyService ! BundledEnvelope(
details.galaxyService ! MessageEnvelope("", SendResponse(details.building.densityLevelUpdateMessage(building))) MessageEnvelope("", GalaxyAction.MapUpdate(details.building.infoUpdateMessage())),
MessageEnvelope("", SendResponse(details.building.densityLevelUpdateMessage(building)))
)
Behaviors.same Behaviors.same
case AmenityStateChange(amenity, data) => 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.objects.sourcing.PlayerSource
import net.psforever.packet.game.{GenericObjectActionMessage, PlanetsideAttributeMessage} import net.psforever.packet.game.{GenericObjectActionMessage, PlanetsideAttributeMessage}
import net.psforever.services.InterstellarClusterService 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.base.message.{GenericObjectAction, PlanetsideAttribute, SendResponse}
import net.psforever.services.galaxy.GalaxyAction import net.psforever.services.galaxy.GalaxyAction
import net.psforever.services.local.support.{CaptureEnvelope, HackCaptureActor, HackClearActor, HackClearEnvelope} import net.psforever.services.local.support.{CaptureEnvelope, HackCaptureActor, HackClearActor, HackClearEnvelope}
@ -204,25 +204,19 @@ case object MajorFacilityLogic
val events = zone.AvatarEvents val events = zone.AvatarEvents
val guid = building.GUID val guid = building.GUID
val msg = GenericObjectAction(guid, 15) val msg = GenericObjectAction(guid, 15)
building.PlayersInSOI.foreach { player => events ! BundledEnvelope(building.PlayersInSOI.map { player => MessageEnvelope(player.Name, msg) })
events ! MessageEnvelope(player.Name, msg)
}
false false
case Some(GeneratorControl.Event.Critical) => case Some(GeneratorControl.Event.Critical) =>
val events = zone.AvatarEvents val events = zone.AvatarEvents
val guid = building.GUID val guid = building.GUID
val msg = PlanetsideAttribute(guid, 46, 1) val msg = PlanetsideAttribute(guid, 46, 1)
building.PlayersInSOI.foreach { player => events ! BundledEnvelope(building.PlayersInSOI.map { player => MessageEnvelope(player.Name, msg) })
events ! MessageEnvelope(player.Name, msg)
}
true true
case Some(GeneratorControl.Event.Destabilized) => case Some(GeneratorControl.Event.Destabilized) =>
val events = zone.AvatarEvents val events = zone.AvatarEvents
val guid = building.GUID val guid = building.GUID
val msg = GenericObjectAction(guid, 16) val msg = GenericObjectAction(guid, 16)
building.PlayersInSOI.foreach { player => events ! BundledEnvelope(building.PlayersInSOI.map { player => MessageEnvelope(player.Name, msg) })
events ! MessageEnvelope(player.Name, msg)
}
if (building.hasCavernLockBenefit) { if (building.hasCavernLockBenefit) {
zone.LocalEvents ! MessageEnvelope( zone.LocalEvents ! MessageEnvelope(
zone.id, zone.id,
@ -235,10 +229,9 @@ case object MajorFacilityLogic
case Some(GeneratorControl.Event.Offline) => case Some(GeneratorControl.Event.Offline) =>
powerLost(details) powerLost(details)
val zone = building.Zone val zone = building.Zone
val events = zone.AvatarEvents
val msg = PlanetsideAttribute(building.GUID, 46, 2) val msg = PlanetsideAttribute(building.GUID, 46, 2)
building.PlayersInSOI.foreach { player => events ! BundledEnvelope(building.PlayersInSOI.map { player => MessageEnvelope(player.Name, msg) }) //???
zone.AvatarEvents ! MessageEnvelope(player.Name, msg)
} //???
true true
case Some(GeneratorControl.Event.Normal) => case Some(GeneratorControl.Event.Normal) =>
true true
@ -253,9 +246,7 @@ case object MajorFacilityLogic
PlanetsideAttributeMessage(guid, 46, 0), PlanetsideAttributeMessage(guid, 46, 0),
GenericObjectActionMessage(guid, 17) GenericObjectActionMessage(guid, 17)
)) ))
building.PlayersInSOI.foreach { player => events ! BundledEnvelope(building.PlayersInSOI.map { player => MessageEnvelope(player.Name, list) })
events ! MessageEnvelope(player.Name, list)
}
true true
case _ => case _ =>
false false

View file

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

View file

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

View file

@ -20,11 +20,12 @@ import net.psforever.objects.zones.Zone
import net.psforever.packet.game._ import net.psforever.packet.game._
import net.psforever.types.{ChatMessageType, ExoSuitType, PlanetSideGUID, Vector3} import net.psforever.types.{ChatMessageType, ExoSuitType, PlanetSideGUID, Vector3}
import net.psforever.services.avatar.AvatarAction 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.base.message.{ObjectDelete, SendResponse}
import net.psforever.services.local.LocalAction import net.psforever.services.local.LocalAction
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.collection.mutable.ArrayBuffer
object Players { object Players {
private val log = org.log4s.getLogger("Players") private val log = org.log4s.getLogger("Players")
@ -49,10 +50,7 @@ object Players {
) { ) {
val events = target.Zone.AvatarEvents val events = target.Zone.AvatarEvents
val uname = user.Name val uname = user.Name
events ! MessageEnvelope( events ! MessageEnvelope(uname, SendResponse(RepairMessage(target.GUID, progress.toInt)))
uname,
SendResponse(RepairMessage(target.GUID, progress.toInt))
)
true true
} else { } else {
false false
@ -439,24 +437,20 @@ object Players {
if ((player.Slot(index).Equipment = obj).contains(obj)) { if ((player.Slot(index).Equipment = obj).contains(obj)) {
val fireMode = tool.FireModeIndex val fireMode = tool.FireModeIndex
val ammoType = tool.AmmoTypeIndex val ammoType = tool.AmmoTypeIndex
val list: ArrayBuffer[MessageEnvelope] = ArrayBuffer()
player.Inventory -= x.start player.Inventory -= x.start
obj.FireModeIndex = fireMode obj.FireModeIndex = fireMode
//TODO any penalty for being handed an OCM version of the tool? //TODO any penalty for being handed an OCM version of the tool?
events ! MessageEnvelope( list.append(MessageEnvelope(zone.id, AvatarAction.EquipmentInHand(pguid, index, obj)))
zone.id,
AvatarAction.EquipmentInHand(pguid, index, obj)
)
if (obj.AmmoTypeIndex != ammoType) { if (obj.AmmoTypeIndex != ammoType) {
obj.AmmoTypeIndex = ammoType obj.AmmoTypeIndex = ammoType
events ! MessageEnvelope( list.append(MessageEnvelope(name, SendResponse(ChangeAmmoMessage(obj.GUID, ammoType))))
name,
SendResponse(ChangeAmmoMessage(obj.GUID, ammoType))
)
} }
if (player.DrawnSlot == Player.HandsDownSlot) { if (player.DrawnSlot == Player.HandsDownSlot) {
player.DrawnSlot = index 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 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.objects.zones.Zone
import net.psforever.packet.game.{ChatMsg, FrameVehicleStateMessage, GenericObjectActionEnum, HackMessage, HackState, HackState1, HackState7, TriggeredSound, VehicleStateMessage} 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.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.base.message.{GenericObjectAction, PlanetsideAttribute, SendResponse, SetEmpire}
import net.psforever.services.local.LocalAction import net.psforever.services.local.LocalAction
import net.psforever.services.vehicle.VehicleAction import net.psforever.services.vehicle.VehicleAction
import scala.collection.mutable.ArrayBuffer
//import scala.concurrent.duration._ //import scala.concurrent.duration._
object Vehicles { object Vehicles {
@ -248,13 +250,15 @@ object Vehicles {
val hFaction = hacker.Faction val hFaction = hacker.Faction
val zone = target.Zone val zone = target.Zone
val zoneid = zone.id val zoneid = zone.id
val avatarEvents = zone.AvatarEvents
val vehicleEvents = zone.VehicleEvents val vehicleEvents = zone.VehicleEvents
val localEvents = zone.LocalEvents
val previousOwnerName = target.OwnerName.getOrElse("") val previousOwnerName = target.OwnerName.getOrElse("")
vehicleEvents ! MessageEnvelope( val occupantMessages: ArrayBuffer[MessageEnvelope] = ArrayBuffer()
val vehicleMessages: ArrayBuffer[MessageEnvelope] = ArrayBuffer()
vehicleMessages.append(MessageEnvelope(
zoneid, zoneid,
SendResponse(HackMessage(HackState1.Unk2, tGuid, hGuid, 100, 0f, HackState.Hacked, HackState7.Unk8)) SendResponse(HackMessage(HackState1.Unk2, tGuid, hGuid, 100, 0f, HackState.Hacked, HackState7.Unk8))
) ))
target.Actor ! CommonMessages.Hack(hacker, target) target.Actor ! CommonMessages.Hack(hacker, target)
// Forcefully dismount any cargo // Forcefully dismount any cargo
target.CargoHolds.foreach { case (_, cargoHold) => target.CargoHolds.foreach { case (_, cargoHold) =>
@ -268,26 +272,29 @@ object Vehicles {
player: Player => player: Player =>
seat.unmount(player) seat.unmount(player)
player.VehicleSeated = None player.VehicleSeated = None
vehicleEvents ! MessageEnvelope( occupantMessages.append(MessageEnvelope(
zoneid, zoneid,
player.GUID, player.GUID,
VehicleAction.KickPassenger(4, unk2 = false, tGuid) VehicleAction.KickPassenger(4, unk2 = false, tGuid)
) ))
} }
// In case BFR is occupied and may or may not be crouched // In case BFR is occupied and may or may not be crouched
if (GlobalDefinitions.isBattleFrameVehicle(target.Definition) && target.Seat(0).isDefined) { if (GlobalDefinitions.isBattleFrameVehicle(target.Definition) && target.Seat(0).isDefined) {
zone.LocalEvents ! MessageEnvelope( vehicleMessages.appendAll(List(
zoneid, MessageEnvelope(zoneid,
GenericObjectAction(target.GUID, GenericObjectActionEnum.BFRShieldsDown.id) GenericObjectAction(target.GUID, GenericObjectActionEnum.BFRShieldsDown.id)
) ),
zone.LocalEvents ! MessageEnvelope( MessageEnvelope(zoneid,
zoneid, SendResponse(
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)
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, MessageEnvelope(zoneid,
SendResponse( SendResponse(
VehicleStateMessage(target.GUID, 0, target.Position, target.Orientation, Some(Vector3(0f, 0f, 0f)), None, 0, 0, 15, is_decelerating=false, is_cloaked=false))) 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 // 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) Vehicles.Own(target, hacker)
//todo: Send HackMessage -> HackCleared to vehicle? can be found in packet captures. Not sure if necessary. //todo: Send HackMessage -> HackCleared to vehicle? can be found in packet captures. Not sure if necessary.
// And broadcast the faction change to other clients // And broadcast the faction change to other clients
zone.AvatarEvents ! MessageEnvelope( vehicleMessages.append(MessageEnvelope(zoneid, SetEmpire(tGuid, hFaction)))
zoneid,
SetEmpire(tGuid, hFaction)
)
} }
localEvents ! MessageEnvelope( vehicleMessages.append(MessageEnvelope(
zoneid, zoneid,
hGuid, hGuid,
LocalAction.TriggerSound(TriggeredSound.HackVehicle, target.Position, 30, 0.49803925f) LocalAction.TriggerSound(TriggeredSound.HackVehicle, target.Position, 30, 0.49803925f)
) ))
if (zone.Players.exists(_.name.equals(previousOwnerName))) { if (zone.Players.exists(_.name.equals(previousOwnerName))) {
localEvents ! MessageEnvelope( vehicleMessages.append(MessageEnvelope(previousOwnerName, SendResponse(ChatMsg(ChatMessageType.UNK_226, "@JackStolen"))))
previousOwnerName,
SendResponse(ChatMsg(ChatMessageType.UNK_226, "@JackStolen"))
)
} }
localEvents ! MessageEnvelope( occupantMessages.append(MessageEnvelope(hacker.Name, SendResponse(ChatMsg(ChatMessageType.UNK_226, "@JackVehicleOwned"))))
hacker.Name,
SendResponse(ChatMsg(ChatMessageType.UNK_226, "@JackVehicleOwned"))
)
// Clean up after specific vehicles, e.g. remove router telepads // Clean up after specific vehicles, e.g. remove router telepads
// If AMS is deployed, swap it to the new faction // If AMS is deployed, swap it to the new faction
target.Definition match { target.Definition match {
@ -343,13 +341,15 @@ object Vehicles {
util.Actor ! TelepadLike.Activate(util) util.Actor ! TelepadLike.Activate(util)
} }
case GlobalDefinitions.ams if target.DeploymentState == DriveState.Deployed => case GlobalDefinitions.ams if target.DeploymentState == DriveState.Deployed =>
vehicleEvents ! MessageEnvelope(zone.id, VehicleAction.AMSDeploymentChange(zone)) vehicleMessages.append(MessageEnvelope(zone.id, VehicleAction.AMSDeploymentChange(zone)))
case _ => () case _ => ()
} }
vehicleEvents ! MessageEnvelope( vehicleMessages.append(MessageEnvelope(
zoneid, zoneid,
SendResponse(HackMessage(HackState1.Unk2, tGuid, tGuid, 0, 1L, HackState.HackCleared, HackState7.Unk8)) SendResponse(HackMessage(HackState1.Unk2, tGuid, tGuid, 0, 1L, HackState.HackCleared, HackState7.Unk8))
) ))
avatarEvents ! BundledEnvelope(occupantMessages)
vehicleEvents ! BundledEnvelope(vehicleMessages)
target.Actor ! CommonMessages.ClearHack() 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._
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.packet.game._ 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.base.message.SetEmpire
import net.psforever.services.local.LocalAction import net.psforever.services.local.LocalAction
import net.psforever.types.PlanetSideEmpire import net.psforever.types.PlanetSideEmpire
@ -198,11 +198,14 @@ trait DeployableBehavior {
None None
} }
//zone build //zone build
localEvents ! MessageEnvelope(zone.id, LocalAction.DeployItem(obj)) localEvents ! BundledEnvelope(
//zone map icon /* zone build */
localEvents ! MessageEnvelope( MessageEnvelope(zone.id, LocalAction.DeployItem(obj)),
obj.Faction.toString, /* zone map icon */
LocalAction.DeployableMapIcon(DeploymentAction.Build, DeployableInfo(obj.GUID, Deployable.Icon(obj.Definition.Item), obj.Position, obj.OwnerGuid.getOrElse(Default.GUID0))) 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 //local build management
callback ! Zone.Deployable.IsBuilt(obj) callback ! Zone.Deployable.IsBuilt(obj)
@ -281,15 +284,13 @@ object DeployableBehavior {
val localEvents = zone.LocalEvents val localEvents = zone.LocalEvents
if (originalFaction != toFaction) { if (originalFaction != toFaction) {
obj.Faction = toFaction obj.Faction = toFaction
//visual tells in regards to ownership by faction localEvents ! BundledEnvelope(
zone.AvatarEvents ! MessageEnvelope( /* visual tells in regards to ownership by faction */
zone.id, MessageEnvelope(zone.id, SetEmpire(dGuid, toFaction)),
SetEmpire(dGuid, toFaction) /* remove knowledge by the previous owner's faction */
) MessageEnvelope(originalFaction.toString, LocalAction.DeployableMapIcon(DeploymentAction.Dismiss, info)),
//remove knowledge by the previous owner's faction /* display to the given faction */
localEvents ! MessageEnvelope( MessageEnvelope(toFaction.toString, LocalAction.DeployableMapIcon(DeploymentAction.Build, info))
originalFaction.toString,
LocalAction.DeployableMapIcon(DeploymentAction.Dismiss, info)
) )
//remove deployable from original owner's toolbox and UI counter //remove deployable from original owner's toolbox and UI counter
zone.AllPlayers.filter(p => obj.OriginalOwnerName.contains(p.Name)) zone.AllPlayers.filter(p => obj.OriginalOwnerName.contains(p.Name))
@ -297,11 +298,6 @@ object DeployableBehavior {
originalOwner.avatar.deployables.Remove(obj) originalOwner.avatar.deployables.Remove(obj)
originalOwner.Zone.LocalEvents ! MessageEnvelope(originalOwner.Name, LocalAction.DeployableUIFor(obj.Definition.Item)) 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.objects.zones.Zone
import net.psforever.packet.game.{GenericObjectActionMessage, ObjectCreateMessage, ObjectDeleteMessage} import net.psforever.packet.game.{GenericObjectActionMessage, ObjectCreateMessage, ObjectDeleteMessage}
import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent 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.base.message.SendResponse
import net.psforever.services.local.LocalAction import net.psforever.services.local.LocalAction
import net.psforever.types.PlanetSideGUID import net.psforever.types.PlanetSideGUID
@ -115,21 +115,23 @@ object TelepadLike {
normally dispatched while the Router is transitioned into its Deploying state 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 it is safe, however, to perform these actions at any time during and after the Deploying state
*/ */
events ! MessageEnvelope( events ! BundledEnvelope(
zoneId, MessageEnvelope(
SendResponse( zoneId,
ObjectCreateMessage( SendResponse(
udef.ObjectId, ObjectCreateMessage(
utilityGUID, udef.ObjectId,
ObjectCreateMessageParent(routerGUID, 2), //TODO stop assuming slot number utilityGUID,
udef.Packet.ConstructorData(obj).get 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) 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.interaction.{Adversarial, DamageResult}
import net.psforever.objects.vital.{DamagingActivity, HealingActivity, InGameActivity, RepairingActivity, RevivingActivity, SpawningActivity} import net.psforever.objects.vital.{DamagingActivity, HealingActivity, InGameActivity, RepairingActivity, RevivingActivity, SpawningActivity}
import net.psforever.services.avatar.AvatarAction 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.types.PlanetSideEmpire
import net.psforever.util.Config import net.psforever.util.Config
@ -52,9 +52,9 @@ object KillAssists {
history: Iterable[InGameActivity], history: Iterable[InGameActivity],
eventBus: ActorRef eventBus: ActorRef
): Unit = { ): Unit = {
rewardThisPlayerDeath(victim, lastDamage, history).foreach { case (p, kda) => eventBus ! BundledEnvelope(rewardThisPlayerDeath(victim, lastDamage, history).map { case (p, kda) =>
eventBus ! MessageEnvelope(p.Name, AvatarAction.UpdateKillsDeathsAssists(p.CharId, 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 akka.actor.Actor
import net.psforever.services.Service import net.psforever.services.Service
import net.psforever.services.base.bus.GenericEventBus 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 import org.log4s.Logger
/** /**
@ -78,6 +78,9 @@ class GenericEventService(stamp: EventSystemStamp)
* Accept and handle designated messages. * Accept and handle designated messages.
*/ */
protected def commonBehavior: Receive = { protected def commonBehavior: Receive = {
case bundle: BundledEnvelope =>
bundle.msgs.foreach(commonBehavior.apply)
case msg: GenericMessageEnvelope => case msg: GenericMessageEnvelope =>
handleMessage(msg) handleMessage(msg)
} }

View file

@ -3,7 +3,7 @@ package net.psforever.services.base
import akka.actor.Cancellable import akka.actor.Cancellable
import net.psforever.objects.Default 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.services.base.message.EventMessage
import net.psforever.types.PlanetSideGUID import net.psforever.types.PlanetSideGUID
import net.psforever.util.Config 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, * Messages that arrive with the same cache profile as a previous message,
* but before that previous message was dispatched, * but before that previous message was dispatched,
* will overwrite the previous message without fanfare or warning. * will overwrite the previous message without fanfare or warning.
* @param event event system message * @param event event system message
* @param eventClassName event system message identifier
* @param eventGuid event system message filter
*/ */
private def pushToCache(event: CachedGenericEventEnvelope): Unit = { private def pushToCache(event: GenericMessageEnvelope, eventClassName: String, eventGuid: PlanetSideGUID): Unit = {
val eventClassName = event.msg.getClass.getName
val updateBranch = cache val updateBranch = cache
.getOrElseUpdate(event.channel, new ConcurrentHashMap[String, CMap[PlanetSideGUID, GenericMessageEnvelope]]().asScala) .getOrElseUpdate(event.channel, new ConcurrentHashMap[String, CMap[PlanetSideGUID, GenericMessageEnvelope]]().asScala)
.getOrElseUpdate(eventClassName, new ConcurrentHashMap[PlanetSideGUID, GenericMessageEnvelope]().asScala) .getOrElseUpdate(eventClassName, new ConcurrentHashMap[PlanetSideGUID, GenericMessageEnvelope]().asScala)
updateBranch.updateWith(event.guid) { _ => Some(event) } updateBranch.updateWith(eventGuid) { _ => Some(event) }
tryRetimeFlushCache() tryRetimeFlushCache()
} }
@ -194,6 +211,8 @@ class GenericEventServiceWithCacheAndSupport
event match { event match {
case FlushCachedMessages => case FlushCachedMessages =>
flushCache() flushCache()
case bundle: BundledEnvelope =>
handleMessageBundled(bundle)
case _: CachedGenericEventEnvelope if tryFlushCache() => case _: CachedGenericEventEnvelope if tryFlushCache() =>
super.handleMessage(event) super.handleMessage(event)
case envelope: CachedGenericEventEnvelope => case envelope: CachedGenericEventEnvelope =>
@ -203,4 +222,24 @@ class GenericEventServiceWithCacheAndSupport
super.handleMessage(event) 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.serverobject.doors.Door
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.services.base.{EventServiceSupport, GenericSupportEnvelope} 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.IsADoorMessage
import net.psforever.services.local.LocalAction import net.psforever.services.local.LocalAction
import net.psforever.types.PlanetSideGUID import net.psforever.types.PlanetSideGUID
@ -64,11 +64,13 @@ class DoorCloseActor() extends Actor {
doorsLeftOpen1 ++ doorsLeftOpen1 ++
doorsLeftOpen2.map(entry => DoorCloseActor.DoorEntry(entry.door, entry.zone, now)) doorsLeftOpen2.map(entry => DoorCloseActor.DoorEntry(entry.door, entry.zone, now))
).sortBy(_.time) ).sortBy(_.time)
doorsToClose2.foreach { case DoorCloseActor.DoorEntry(door, zone, _) => doorsToClose2
door.Open = None //permissible break from synchronization .map { case DoorCloseActor.DoorEntry(door, zone, _) =>
zone.LocalEvents ! MessageEnvelope(zone.id, LocalAction.DoorCloses(door.GUID)) //call up to the main event system 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) { if (openDoors.nonEmpty) {
val short_timeout: FiniteDuration = math.max(1, DoorCloseActor.timeout_time - (now - openDoors.head.time)).milliseconds val short_timeout: FiniteDuration = math.max(1, DoorCloseActor.timeout_time - (now - openDoors.head.time)).milliseconds
import scala.concurrent.ExecutionContext.Implicits.global 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.packet.game.{ChatMsg, GenericAction, HackState7, PlanetsideAttributeEnum}
import net.psforever.objects.sourcing.PlayerSource import net.psforever.objects.sourcing.PlayerSource
import net.psforever.services.base.{EventServiceSupport, GenericSupportEnvelopeOnly} 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.base.message.PlanetsideAttribute
import net.psforever.services.local.support.HackCaptureActor.GetHackingFaction import net.psforever.services.local.support.HackCaptureActor.GetHackingFaction
import net.psforever.services.local.LocalAction import net.psforever.services.local.LocalAction
@ -22,6 +22,7 @@ import net.psforever.types.{ChatMessageType, PlanetSideEmpire, PlanetSideGUID}
import java.util.concurrent.{Executors, TimeUnit} import java.util.concurrent.{Executors, TimeUnit}
import scala.collection.Seq import scala.collection.Seq
import scala.collection.mutable.ArrayBuffer
import scala.concurrent.duration.{FiniteDuration, _} import scala.concurrent.duration.{FiniteDuration, _}
import scala.util.Random import scala.util.Random
@ -277,18 +278,18 @@ class HackCaptureActor extends Actor {
private def HackCompleted(terminal: CaptureTerminal with Hackable, hackedByFaction: PlanetSideEmpire.Value): Unit = { private def HackCompleted(terminal: CaptureTerminal with Hackable, hackedByFaction: PlanetSideEmpire.Value): Unit = {
val building = terminal.Owner.asInstanceOf[Building] val building = terminal.Owner.asInstanceOf[Building]
val zone = building.Zone
val events = zone.LocalEvents
val messages: ArrayBuffer[MessageEnvelope] = ArrayBuffer()
if (building.NtuLevel > 0) { if (building.NtuLevel > 0) {
building.virusId = 8 building.virusId = 8
building.virusInstalledBy = None building.virusInstalledBy = None
log.info(s"Setting base ${building.GUID} / MapId: ${building.MapId} as owned by $hackedByFaction") 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 //dispatch to players aligned with the capturing faction within the SOI
val events = building.Zone.LocalEvents
val msg = LocalAction.GenericActionMessage(GenericAction.FacilityCaptureFanfare) val msg = LocalAction.GenericActionMessage(GenericAction.FacilityCaptureFanfare)
building messages.appendAll(building.PlayersInSOI.collect { case p if p.Faction == hackedByFaction =>
.PlayersInSOI MessageEnvelope(p.Name, msg)
.collect { case p if p.Faction == hackedByFaction => })
events ! MessageEnvelope(p.Name, msg)
}
val buildings = building.Zone.Buildings.values val buildings = building.Zone.Buildings.values
val hackedBaseId = building.GUID val hackedBaseId = building.GUID
val facilities = if (building.Zone.id.startsWith("c")) { val facilities = if (building.Zone.id.startsWith("c")) {
@ -322,8 +323,8 @@ class HackCaptureActor extends Actor {
} }
NotifyHackStateChange(terminal, isResecured = true) 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. // 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 messages.append(MessageEnvelope(zone.id, LocalAction.HackClear(building.GUID, 3212836864L, HackState7.Unk8)))
zone.LocalEvents ! MessageEnvelope(zone.id, LocalAction.HackClear(building.GUID, 3212836864L, HackState7.Unk8)) events ! BundledEnvelope(messages)
} }
private def RestartTimer(): Unit = { 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.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.packet.game.HackState7 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.{EventServiceSupport, GenericSupportEnvelope, GenericSupportEnvelopeOnly}
import net.psforever.services.base.message.GenericObjectAction import net.psforever.services.base.message.GenericObjectAction
import net.psforever.services.local.LocalAction.IsAHackMessage 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 //TODO we can just walk across the list of doors and extract only the first few entries
val (unhackObjects, stillHackedObjects) = PartitionEntries(hackedObjects, now) val (unhackObjects, stillHackedObjects) = PartitionEntries(hackedObjects, now)
hackedObjects = stillHackedObjects hackedObjects = stillHackedObjects
unhackObjects.foreach { case HackClearActor.HackEntry(target, zone, unk1, unk2, _, _) => unhackObjects
target.Actor ! CommonMessages.ClearHack() .map { case HackClearActor.HackEntry(target, zone, unk1, unk2, _, _) =>
zone.LocalEvents ! MessageEnvelope(zone.id, LocalAction.HackClear(target.GUID, unk1, unk2)) target.Actor ! CommonMessages.ClearHack()
if (target.Definition == GlobalDefinitions.main_terminal) { if (target.Definition == GlobalDefinitions.main_terminal) {
ClearVirusFromBuilding(target) 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() RestartTimer()
@ -132,9 +137,7 @@ class HackClearActor() extends Actor {
building.virusInstalledBy = None building.virusInstalledBy = None
val msg = GenericObjectAction(target.GUID, 60) val msg = GenericObjectAction(target.GUID, 60)
val events = building.Zone.AvatarEvents val events = building.Zone.AvatarEvents
building.PlayersInSOI.foreach { player => events ! BundledEnvelope(building.PlayersInSOI.map { player => MessageEnvelope(player.Name, msg) })
events ! MessageEnvelope(player.Name, msg)
}
building.Actor ! BuildingActor.MapUpdate() building.Actor ! BuildingActor.MapUpdate()
} }