From 4f50884c7102abcddb0c5cac97457d794eac2885 Mon Sep 17 00:00:00 2001 From: ScrawnyRonnie Date: Mon, 21 Jul 2025 10:38:08 -0400 Subject: [PATCH] density-alerts --- src/main/resources/application.conf | 12 ++++++ .../session/support/ZoningOperations.scala | 2 +- .../psforever/actors/zone/BuildingActor.scala | 7 +++ .../serverobject/structures/Building.scala | 43 ++++++++++++++++++- .../MajorFacilityHackParticipation.scala | 11 +++++ .../zones/SphereOfInfluenceActor.scala | 9 +++- .../scala/net/psforever/util/Config.scala | 9 +++- 7 files changed, 89 insertions(+), 4 deletions(-) diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index cbb9bc88..6d56004c 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -508,6 +508,18 @@ game { capture-experience-points-modifier = 1f # Don't forget to pay back that debt. } + + alert { + # When a certain number of enemy players are within the SOI of a facility, an alert (DensityLevelUpdateMessage) + # will be dispatched to all players. Players of the owning faction will receive a chat warning (if in + # the same zone) and the map will flash the alert level over the facility until it changes + # Wiki says 25-30 + yellow = 1 + # Wiki says 30-60 + orange = 2 + # Wiki says 60+ + red = 3 + } } anti-cheat { diff --git a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala index a60ac1ed..503e7757 100644 --- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala @@ -972,7 +972,7 @@ class ZoningOperations( */ def initFacility(continentNumber: Int, buildingNumber: Int, building: Building): Unit = { sendResponse(building.infoUpdateMessage()) - sendResponse(DensityLevelUpdateMessage(continentNumber, buildingNumber, List(0, 0, 0, 0, 0, 0, 0, 0))) + sendResponse(building.densityLevelUpdateMessage(building)) } /** diff --git a/src/main/scala/net/psforever/actors/zone/BuildingActor.scala b/src/main/scala/net/psforever/actors/zone/BuildingActor.scala index 8cfd403d..33492124 100644 --- a/src/main/scala/net/psforever/actors/zone/BuildingActor.scala +++ b/src/main/scala/net/psforever/actors/zone/BuildingActor.scala @@ -74,6 +74,8 @@ object BuildingActor { final case class PowerOff() extends Command + final case class DensityLevelUpdate(building: Building) extends Command + /** * Set a facility affiliated to one faction to be affiliated to a different faction. * @param details building and event system references @@ -226,6 +228,7 @@ class BuildingActor( case MapUpdate() => details.galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(details.building.infoUpdateMessage())) + details.galaxyService ! GalaxyServiceMessage(GalaxyAction.SendResponse(details.building.densityLevelUpdateMessage(building))) Behaviors.same case AmenityStateChange(amenity, data) => @@ -245,6 +248,10 @@ class BuildingActor( case Ntu(msg) => logic.ntu(details, msg) + + case DensityLevelUpdate(building) => + details.galaxyService ! GalaxyServiceMessage(GalaxyAction.SendResponse(details.building.densityLevelUpdateMessage(building))) + Behaviors.same } } } diff --git a/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala b/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala index cf2634b2..adcf381b 100644 --- a/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala +++ b/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala @@ -11,13 +11,14 @@ import net.psforever.objects.serverobject.resourcesilo.ResourceSilo import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects.zones.Zone import net.psforever.objects.zones.blockmap.BlockMapEntity -import net.psforever.packet.game.BuildingInfoUpdateMessage +import net.psforever.packet.game.{BuildingInfoUpdateMessage, DensityLevelUpdateMessage} import net.psforever.types._ import scalax.collection.{Graph, GraphEdge} import akka.actor.typed.scaladsl.adapter._ import net.psforever.objects.serverobject.llu.{CaptureFlag, CaptureFlagSocket} import net.psforever.objects.serverobject.structures.participation.{MajorFacilityHackParticipation, NoParticipation, ParticipationLogic, TowerHackParticipation} import net.psforever.objects.serverobject.terminals.capture.CaptureTerminal +import net.psforever.util.Config class Building( private val name: String, @@ -236,6 +237,46 @@ class Building( ) } + def densityLevelUpdateMessage(building: Building): DensityLevelUpdateMessage = { + if (building.PlayersInSOI.nonEmpty) { + val factionCounts: Map[PlanetSideEmpire.Value, Int] = + building.PlayersInSOI.groupBy(_.Faction).view.mapValues(_.size).toMap + val otherEmpireCounts: Map[PlanetSideEmpire.Value, Int] = PlanetSideEmpire.values.map { + faction => + val otherCount = factionCounts.filterNot(_._1 == faction).values.sum + faction -> otherCount + }.toMap + val trAlert = otherEmpireCounts.getOrElse(PlanetSideEmpire.TR, 0) match { + case count if count >= Config.app.game.alert.red => 3 + case count if count >= Config.app.game.alert.orange => 2 + case count if count >= Config.app.game.alert.yellow => 1 + case _ => 0 + } + val ncAlert = otherEmpireCounts.getOrElse(PlanetSideEmpire.NC, 0) match { + case count if count >= Config.app.game.alert.red => 3 + case count if count >= Config.app.game.alert.orange => 2 + case count if count >= Config.app.game.alert.yellow => 1 + case _ => 0 + } + val vsAlert = otherEmpireCounts.getOrElse(PlanetSideEmpire.VS, 0) match { + case count if count >= Config.app.game.alert.red => 3 + case count if count >= Config.app.game.alert.orange => 2 + case count if count >= Config.app.game.alert.yellow => 1 + case _ => 0 + } + val boAlert = otherEmpireCounts.getOrElse(PlanetSideEmpire.NEUTRAL, 0) match { + case count if count >= Config.app.game.alert.red => 3 + case count if count >= Config.app.game.alert.orange => 2 + case count if count >= Config.app.game.alert.yellow => 1 + case _ => 0 + } + DensityLevelUpdateMessage(Zone.Number, MapId, List(0, trAlert, 0, ncAlert, 0, vsAlert, 0, boAlert)) + } + else { //nobody is in this SOI + DensityLevelUpdateMessage(Zone.Number, MapId, List(0, 0, 0, 0, 0, 0, 0, 0)) + } + } + def hasLatticeBenefit(wantedBenefit: LatticeBenefit): Boolean = { val baseDownState = (NtuSource match { case Some(ntu) => ntu.NtuCapacitor < 1f diff --git a/src/main/scala/net/psforever/objects/serverobject/structures/participation/MajorFacilityHackParticipation.scala b/src/main/scala/net/psforever/objects/serverobject/structures/participation/MajorFacilityHackParticipation.scala index 230aa222..bb319dcc 100644 --- a/src/main/scala/net/psforever/objects/serverobject/structures/participation/MajorFacilityHackParticipation.scala +++ b/src/main/scala/net/psforever/objects/serverobject/structures/participation/MajorFacilityHackParticipation.scala @@ -9,6 +9,7 @@ import net.psforever.types.{ChatMessageType, PlanetSideEmpire, Vector3} import net.psforever.util.Config import akka.pattern.ask import akka.util.Timeout +import net.psforever.actors.zone.BuildingActor import net.psforever.objects.Player import net.psforever.objects.avatar.scoring.Kill import net.psforever.objects.serverobject.hackable.Hackable @@ -26,6 +27,8 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci private var hotSpotLayersOverTime: Seq[List[HotSpotInfo]] = Seq[List[HotSpotInfo]]() + var lastEnemyCount: List[Player] = List.empty + def TryUpdate(): Unit = { val list = building.PlayersInSOI if (list.nonEmpty) { @@ -37,6 +40,14 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci updateHotSpotInfoOverTime() updateTime(now) } + val enemies = list.filter(p => p.Faction != building.Faction) ++ + building.Zone.blockMap.sector(building).corpseList + .filter(p => Vector3.DistanceSquared(building.Position.xy, p.Position.xy) < building.Definition.SOIRadius * building.Definition.SOIRadius) + //alert defenders (actually goes to all clients) of population change for base alerts + if (Math.abs(enemies.length - lastEnemyCount.length) >= 1) { + building.Actor ! BuildingActor.DensityLevelUpdate(building) + } + lastEnemyCount = enemies building.CaptureTerminal .map(_.HackedBy) .collect { diff --git a/src/main/scala/net/psforever/objects/zones/SphereOfInfluenceActor.scala b/src/main/scala/net/psforever/objects/zones/SphereOfInfluenceActor.scala index 0510d5c0..4c5a4171 100644 --- a/src/main/scala/net/psforever/objects/zones/SphereOfInfluenceActor.scala +++ b/src/main/scala/net/psforever/objects/zones/SphereOfInfluenceActor.scala @@ -55,9 +55,16 @@ class SphereOfInfluenceActor(zone: Zone) extends Actor { sois.foreach { case (facility, radius) => val facilityXY = facility.Position.xy - facility.PlayersInSOI = zone.blockMap.sector(facility) + val playersOnFoot = zone.blockMap.sector(facility) .livePlayerList .filter(p => Vector3.DistanceSquared(facilityXY, p.Position.xy) < radius) + + val vehicleOccupants = zone.blockMap.sector(facility) + .vehicleList + .filter(v => Vector3.DistanceSquared(facilityXY, v.Position.xy) < radius) + .flatMap(_.Seats.values.flatMap(_.occupants)) + + facility.PlayersInSOI = playersOnFoot ++ vehicleOccupants } populateTick.cancel() populateTick = context.system.scheduler.scheduleOnce(5 seconds, self, SOI.Populate()) diff --git a/src/main/scala/net/psforever/util/Config.scala b/src/main/scala/net/psforever/util/Config.scala index 13d7d5c9..ef131424 100644 --- a/src/main/scala/net/psforever/util/Config.scala +++ b/src/main/scala/net/psforever/util/Config.scala @@ -164,7 +164,8 @@ case class GameConfig( experience: Experience, maxBattleRank: Int, promotion: PromotionSystem, - facilityHackTime: FiniteDuration + facilityHackTime: FiniteDuration, + alert: DensityAlert ) case class InstantActionConfig( @@ -326,3 +327,9 @@ case class PromotionSystem( supportExperiencePointsModifier: Float, captureExperiencePointsModifier: Float ) + +case class DensityAlert( + yellow: Int, + orange: Int, + red: Int +)