From 4d3a3a390fcf68864aeb8ed9daaf7cedab6cd5bf Mon Sep 17 00:00:00 2001 From: ScrawnyRonnie Date: Sun, 29 Jun 2025 15:58:47 -0400 Subject: [PATCH 01/19] max armor reminder, log kicked player --- .../net/psforever/actors/session/support/ZoningOperations.scala | 1 + .../psforever/services/account/AccountPersistenceService.scala | 1 + 2 files changed, 2 insertions(+) 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 49bb0833c..514b7c3a6 100644 --- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala @@ -3134,6 +3134,7 @@ class ZoningOperations( statisticsPacketFunc() if (tplayer.ExoSuit == ExoSuitType.MAX) { sendResponse(PlanetsideAttributeMessage(guid, 7, tplayer.Capacitor.toLong)) + sendResponse(PlanetsideAttributeMessage(guid, 4, tplayer.Armor)) } // AvatarAwardMessage //populateAvatarAwardRibbonsFunc(1, 20L) diff --git a/src/main/scala/net/psforever/services/account/AccountPersistenceService.scala b/src/main/scala/net/psforever/services/account/AccountPersistenceService.scala index 1d2fe0f4f..3ff3206af 100644 --- a/src/main/scala/net/psforever/services/account/AccountPersistenceService.scala +++ b/src/main/scala/net/psforever/services/account/AccountPersistenceService.scala @@ -318,6 +318,7 @@ class PersistenceMonitor( } kicked = true kickTime = time.orElse(Some(300L)) + PerformLogout() case Logout(_) => kickTime match { From e76c33b0285f0536395878e796149880094b991e Mon Sep 17 00:00:00 2001 From: ScrawnyRonnie Date: Mon, 30 Jun 2025 13:00:58 -0400 Subject: [PATCH 02/19] tell others my armor --- .../net/psforever/actors/session/support/ZoningOperations.scala | 1 + 1 file changed, 1 insertion(+) 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 514b7c3a6..f7efba70c 100644 --- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala @@ -3135,6 +3135,7 @@ class ZoningOperations( if (tplayer.ExoSuit == ExoSuitType.MAX) { sendResponse(PlanetsideAttributeMessage(guid, 7, tplayer.Capacitor.toLong)) sendResponse(PlanetsideAttributeMessage(guid, 4, tplayer.Armor)) + continent.AvatarEvents ! AvatarServiceMessage(continent.id, AvatarAction.PlanetsideAttributeToAll(guid, 4, tplayer.Armor)) } // AvatarAwardMessage //populateAvatarAwardRibbonsFunc(1, 20L) From e3231bae022784f477fa81b9eb5c7b6d983769d4 Mon Sep 17 00:00:00 2001 From: ScrawnyRonnie Date: Mon, 30 Jun 2025 20:28:16 -0400 Subject: [PATCH 03/19] tell me armor for any max --- .../net/psforever/actors/session/support/ZoningOperations.scala | 2 ++ 1 file changed, 2 insertions(+) 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 f7efba70c..3cb7d7643 100644 --- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala @@ -3137,6 +3137,8 @@ class ZoningOperations( sendResponse(PlanetsideAttributeMessage(guid, 4, tplayer.Armor)) continent.AvatarEvents ! AvatarServiceMessage(continent.id, AvatarAction.PlanetsideAttributeToAll(guid, 4, tplayer.Armor)) } + // for issue #1269 + continent.AllPlayers.filter(_.ExoSuit == ExoSuitType.MAX).foreach(max => sendResponse(PlanetsideAttributeMessage(max.GUID, 4, max.Armor))) // AvatarAwardMessage //populateAvatarAwardRibbonsFunc(1, 20L) From b82e0280f6e2979aa7c73ac4b6a71e39359ba936 Mon Sep 17 00:00:00 2001 From: ScrawnyRonnie Date: Sun, 6 Jul 2025 15:24:07 -0400 Subject: [PATCH 04/19] gate fixes --- .../session/support/ZoningOperations.scala | 3 ++- .../net/psforever/objects/SpawnPoint.scala | 4 +--- .../global/GlobalDefinitionsBuilding.scala | 8 ++++---- .../scala/net/psforever/zones/Zones.scala | 20 ++++++++++++++++++- 4 files changed, 26 insertions(+), 9 deletions(-) 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 3cb7d7643..a60ac1edc 100644 --- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala @@ -1269,6 +1269,7 @@ class ZoningOperations( ICS.FindZone(_.id == zoneId, context.self) )) } else { + vehicle.Velocity = None sessionLogic.general.unaccessContainer(vehicle) LoadZoneCommonTransferActivity() player.VehicleSeated = vehicle.GUID @@ -2965,7 +2966,7 @@ class ZoningOperations( /** * na * @param target player being spawned - * @param position where player is being placed in the game wqrld + * @param position where player is being placed in the game world * @param orientation in what direction the player is facing in the game world * @param onThisSide description of the containing environment * @param goingToZone common designation for the zone diff --git a/src/main/scala/net/psforever/objects/SpawnPoint.scala b/src/main/scala/net/psforever/objects/SpawnPoint.scala index d6f800dde..e3fc8ca52 100644 --- a/src/main/scala/net/psforever/objects/SpawnPoint.scala +++ b/src/main/scala/net/psforever/objects/SpawnPoint.scala @@ -140,8 +140,6 @@ object SpawnPoint { def CavernGate(innerRadius: Float)(obj: SpawnPoint, target: PlanetSideGameObject): (Vector3, Vector3) = { val (a, b) = metaGate(obj, target, innerRadius) target match { - case v: Vehicle if GlobalDefinitions.isFlightVehicle(v.Definition) => - (a.xy + Vector3.z((target.Position.z + a.z) * 0.5f), b) case m: MountableEntity => m.BailProtection = true (a + Vector3.z(obj.Definition.UseRadius * 0.5f), b) @@ -154,7 +152,7 @@ object SpawnPoint { val (a, b) = metaGate(obj, target, innerRadius) target match { case v: Vehicle if GlobalDefinitions.isFlightVehicle(v.Definition) => - (a.xy + Vector3.z((target.Position.z + a.z) * 0.5f), b) + (a, b) case _ => (a + Vector3.z(flightlessZOffset), b) } diff --git a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsBuilding.scala b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsBuilding.scala index 74a487821..ca18a2e0a 100644 --- a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsBuilding.scala +++ b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsBuilding.scala @@ -129,8 +129,8 @@ object GlobalDefinitionsBuilding { hst.NoWarp += colossus_flight hst.NoWarp += peregrine_gunner hst.NoWarp += peregrine_flight - hst.SpecificPointFunc = SpawnPoint.CavernGate(innerRadius = 6f) - + hst.SpecificPointFunc = SpawnPoint.CavernGate(innerRadius = 12f) + warpgate.Name = "warpgate" warpgate.UseRadius = 67.81070029f warpgate.SOIRadius = 302 //301.8713f @@ -141,8 +141,8 @@ object GlobalDefinitionsBuilding { warpgate_cavern.UseRadius = 19.72639434f warpgate_cavern.SOIRadius = 41 warpgate_cavern.VehicleAllowance = true - warpgate_cavern.SpecificPointFunc = SpawnPoint.CavernGate(innerRadius = 4.5f) - + warpgate_cavern.SpecificPointFunc = SpawnPoint.CavernGate(innerRadius = 12f) + warpgate_small.Name = "warpgate_small" warpgate_small.UseRadius = 69.03687655f warpgate_small.SOIRadius = 103 diff --git a/src/main/scala/net/psforever/zones/Zones.scala b/src/main/scala/net/psforever/zones/Zones.scala index 6f9694688..f1463dfda 100644 --- a/src/main/scala/net/psforever/zones/Zones.scala +++ b/src/main/scala/net/psforever/zones/Zones.scala @@ -295,7 +295,25 @@ object Zones { WarpGate.Structure(Vector3(structure.absX, structure.absY, structure.absZ), GlobalDefinitions.hst) ) ) - case objectType if warpGateTypes.contains(objectType) => + case objectType @ "warpgate_cavern" if warpGateTypes.contains(objectType) => + zoneMap.addLocalBuilding( + structure.objectName, + structure.guid, + structure.mapId.get, + FoundationBuilder( + WarpGate.Structure(Vector3(structure.absX, structure.absY, structure.absZ), GlobalDefinitions.warpgate_cavern) + ) + ) + case objectType @ "warpgate_small" if warpGateTypes.contains(objectType) => + zoneMap.addLocalBuilding( + structure.objectName, + structure.guid, + structure.mapId.get, + FoundationBuilder( + WarpGate.Structure(Vector3(structure.absX, structure.absY, structure.absZ), GlobalDefinitions.warpgate_small) + ) + ) + case objectType @ "warpgate" if warpGateTypes.contains(objectType) => zoneMap.addLocalBuilding( structure.objectName, structure.guid, From 95b8b3459473e1f814e01af13c15122bddf7dc74 Mon Sep 17 00:00:00 2001 From: ScrawnyRonnie Date: Thu, 17 Jul 2025 15:35:10 -0400 Subject: [PATCH 05/19] cud emp and os --- .../resources/overrides/game_objects0.adb.lst | 2 +- .../actors/session/AvatarActor.scala | 66 +++++++++++- .../actors/session/SessionActor.scala | 3 +- .../normal/WeaponAndProjectileLogic.scala | 6 +- .../spectator/WeaponAndProjectileLogic.scala | 13 +-- .../WeaponAndProjectileOperations.scala | 80 +++++++++++++- .../session/support/ZoningOperations.scala | 2 +- .../net/psforever/objects/OrbitalStrike.scala | 61 +++++++++++ .../net/psforever/objects/SpecialEmp.scala | 102 ++++++++++++++++++ .../net/psforever/objects/avatar/Avatar.scala | 7 ++ .../objects/avatar/PlayerControl.scala | 6 ++ .../net/psforever/objects/zones/Zone.scala | 47 ++++++++ .../game/PlanetsideAttributeMessage.scala | 4 + .../psforever/packet/game/UplinkRequest.scala | 6 +- 14 files changed, 382 insertions(+), 23 deletions(-) create mode 100644 src/main/scala/net/psforever/objects/OrbitalStrike.scala diff --git a/server/src/main/resources/overrides/game_objects0.adb.lst b/server/src/main/resources/overrides/game_objects0.adb.lst index d476e61a9..1ea38dca0 100644 --- a/server/src/main/resources/overrides/game_objects0.adb.lst +++ b/server/src/main/resources/overrides/game_objects0.adb.lst @@ -23,7 +23,7 @@ add_property boomer_trigger equiptime 500 add_property chainblade equiptime 250 add_property chainblade holstertime 250 add_property colossus_flight requirement_award0 false -add_property command_detonater allowed false +add_property command_detonater allowed true add_property command_detonater equiptime 500 add_property command_detonater holstertime 500 add_property cycler equiptime 600 diff --git a/src/main/scala/net/psforever/actors/session/AvatarActor.scala b/src/main/scala/net/psforever/actors/session/AvatarActor.scala index c68b0f47c..12291c4ac 100644 --- a/src/main/scala/net/psforever/actors/session/AvatarActor.scala +++ b/src/main/scala/net/psforever/actors/session/AvatarActor.scala @@ -154,6 +154,10 @@ object AvatarActor { final case class UpdatePurchaseTime(definition: BasicDefinition, time: LocalDateTime = LocalDateTime.now()) extends Command + /** rchase time for the use of calculating cooldowns */ + final case class UpdateCUDTime(action: String, time: LocalDateTime = LocalDateTime.now()) + extends Command + /** Set use time for the use of calculating cooldowns */ final case class UpdateUseTime(definition: BasicDefinition, time: LocalDateTime = LocalDateTime.now()) extends Command @@ -459,7 +463,15 @@ object AvatarActor { case _ => () } } catch { - case _: Exception => () + case _: Exception => + val cooldown = LocalDateTime.parse(b) + name match { + case "orbital_strike" if now.compareTo(cooldown.plusMillis(3.hours.toMillis.toInt)) == -1 => + cooldowns.put(name, cooldown) + case "emp_blast" | "reveal_friendlies" | "reveal_enemies" if now.compareTo(cooldown.plusMillis(20.minutes.toMillis.toInt)) == -1 => + cooldowns.put(name, cooldown) + case _ => () + } } case _ => log.warn(s"ignoring invalid cooldown string: '$value'") @@ -1549,6 +1561,21 @@ class AvatarActor( } Behaviors.same + case UpdateCUDTime(action, time) => + var theTimes = avatar.cooldowns.purchase + var updateTheTimes: Boolean = false + Avatar.cudCooldowns.get(action) match { + case Some(_) => + //only send for items with cooldowns + updateTheTimes = true + theTimes = theTimes.updated(action, time) + case _ => () + } + if (updateTheTimes) { + avatarCopy(avatar.copy(cooldowns = avatar.cooldowns.copy(purchase = theTimes))) + } + Behaviors.same + case UpdateUseTime(definition, time) => if (!Avatar.useCooldowns.contains(definition)) { log.warn(s"${avatar.name} is updating a use time for item '${definition.Name}' that has no cooldown") @@ -2655,6 +2682,43 @@ class AvatarActor( } if (keysToDrop.nonEmpty) { val cdown = avatar.cooldowns + val cud = Array("orbital_strike", "emp_blast", "reveal_friendlies", "reveal_enemies") + keysToDrop.foreach { key => + if (cud.contains(key)) { + avatar + .cooldowns + .purchase + .find { case (name, _) => name.equals(key) } + .flatMap { case (name, purchaseTime) => + val secondsSincePurchase = Seconds.secondsBetween(purchaseTime, LocalDateTime.now()).getSeconds + Avatar + .cudCooldowns + .find(_._1.equals(name)) + .collect { + case (action, cooldown) => + (action, cooldown.toSeconds - secondsSincePurchase) + } + .orElse { + None + } + .collect { + case (action, remainingTime) if remainingTime > 0 => + val convertTime = remainingTime * 1000 + keysToDrop = keysToDrop.filterNot(_ == action) + action match { + case "orbital_strike" => + sessionActor ! SessionActor.SendResponse(PlanetsideAttributeMessage(session.get.player.GUID, 60, convertTime)) + case "emp_blast" => + sessionActor ! SessionActor.SendResponse(PlanetsideAttributeMessage(session.get.player.GUID, 59, convertTime)) + case "reveal_enemies" => + sessionActor ! SessionActor.SendResponse(PlanetsideAttributeMessage(session.get.player.GUID, 58, convertTime)) + case "reveal_friendlies" => + sessionActor ! SessionActor.SendResponse(PlanetsideAttributeMessage(session.get.player.GUID, 57, convertTime)) + } + } + } + } + } avatarCopy(avatar.copy(cooldowns = cdown.copy(purchase = cdown.purchase.removedAll(keysToDrop)))) } } diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala index 899d32651..25b5c377e 100644 --- a/src/main/scala/net/psforever/actors/session/SessionActor.scala +++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala @@ -517,7 +517,8 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con case packet: WeaponLazeTargetPositionMessage => logic.shooting.handleWeaponLazeTargetPosition(packet) - case _: UplinkRequest => () + case packet: UplinkRequest => + logic.shooting.handleUplinkRequest(packet) case packet: HitMessage => logic.shooting.handleDirectHit(packet) diff --git a/src/main/scala/net/psforever/actors/session/normal/WeaponAndProjectileLogic.scala b/src/main/scala/net/psforever/actors/session/normal/WeaponAndProjectileLogic.scala index 0d33f5aba..785cb06c2 100644 --- a/src/main/scala/net/psforever/actors/session/normal/WeaponAndProjectileLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/WeaponAndProjectileLogic.scala @@ -8,8 +8,8 @@ import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObjec import net.psforever.objects.{BoomerDeployable, BoomerTrigger, Player, SpecialEmp, Tool, Vehicle} import net.psforever.objects.vital.base.{DamageResolution, DamageType} import net.psforever.objects.zones.{Zone, ZoneProjectile} -import net.psforever.packet.game.{AIDamage, AvatarGrenadeStateMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, HitMessage, LashMessage, LongRangeProjectileInfoMessage, ProjectileStateMessage, ReloadMessage, SplashHitMessage, UplinkRequest, WeaponDelayFireMessage, WeaponDryFireMessage, WeaponFireMessage, WeaponLazeTargetPositionMessage} -import net.psforever.types.Vector3 +import net.psforever.packet.game.{AIDamage, AvatarGrenadeStateMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, HitMessage, LashMessage, LongRangeProjectileInfoMessage, OrbitalStrikeWaypointMessage, ProjectileStateMessage, ReloadMessage, SplashHitMessage, TriggerEffectMessage, TriggeredEffectLocation, UplinkRequest, UplinkRequestType, UplinkResponse, WeaponDelayFireMessage, WeaponDryFireMessage, WeaponFireMessage, WeaponLazeTargetPositionMessage} +import net.psforever.types.{ValidPlanetSideGUID, Vector3} object WeaponAndProjectileLogic { def apply(ops: WeaponAndProjectileOperations): WeaponAndProjectileLogic = { @@ -50,7 +50,7 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit } def handleUplinkRequest(packet: UplinkRequest): Unit = { - sessionLogic.administrativeKick(player) + ops.handleUplinkRequest(packet) } def handleAvatarGrenadeState(pkt: AvatarGrenadeStateMessage): Unit = { diff --git a/src/main/scala/net/psforever/actors/session/spectator/WeaponAndProjectileLogic.scala b/src/main/scala/net/psforever/actors/session/spectator/WeaponAndProjectileLogic.scala index 89a95dac1..ab5002760 100644 --- a/src/main/scala/net/psforever/actors/session/spectator/WeaponAndProjectileLogic.scala +++ b/src/main/scala/net/psforever/actors/session/spectator/WeaponAndProjectileLogic.scala @@ -28,18 +28,7 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit def handleWeaponLazeTargetPosition(pkt: WeaponLazeTargetPositionMessage): Unit = { /* intentionally blank */ } - def handleUplinkRequest(packet: UplinkRequest): Unit = { - val UplinkRequest(code, _, _) = packet - val playerFaction = player.Faction - //todo this is not correct - code match { - case UplinkRequestType.RevealFriendlies => - sendResponse(UplinkResponse(code.value, continent.LivePlayers.count(_.Faction == playerFaction))) - case UplinkRequestType.RevealEnemies => - sendResponse(UplinkResponse(code.value, continent.LivePlayers.count(_.Faction != playerFaction))) - case _ => () - } - } + def handleUplinkRequest(packet: UplinkRequest): Unit = { /* intentionally blank */ } def handleAvatarGrenadeState(pkt: AvatarGrenadeStateMessage): Unit = { /* intentionally blank */ } diff --git a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala index 5ade93f01..eebb56466 100644 --- a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala @@ -3,6 +3,8 @@ package net.psforever.actors.session.support import akka.actor.{ActorContext, typed} import net.psforever.login.WorldSession.{CountAmmunition, CountGrenades, FindAmmoBoxThatUses, FindEquipmentStock, FindToolThatUses, PutEquipmentInInventoryOrDrop, PutNewEquipmentInInventoryOrDrop, RemoveOldEquipmentFromInventory} +import net.psforever.objects.OrbitalStrike.{cr4_os, cr5_os} +import net.psforever.objects.SpecialEmp.{cr3_emp, cr4_emp, cr5_emp} import net.psforever.objects.ballistics.ProjectileQuality import net.psforever.objects.definition.{ProjectileDefinition, SpecialExoSuitDefinition} import net.psforever.objects.entity.SimpleWorldEntity @@ -23,7 +25,9 @@ import net.psforever.objects.vital.etc.OicwLilBuddyReason import net.psforever.objects.vital.interaction.DamageInteraction import net.psforever.objects.vital.projectile.ProjectileReason import net.psforever.objects.zones.exp.ToDatabase -import net.psforever.types.{ChatMessageType, Vector3} +import net.psforever.packet.game.UplinkRequest +import net.psforever.services.local.{LocalAction, LocalServiceMessage} +import net.psforever.types.{ChatMessageType, PlanetSideEmpire, ValidPlanetSideGUID, Vector3} import net.psforever.util.Config import scala.collection.mutable @@ -89,6 +93,7 @@ class WeaponAndProjectileOperations( ) extends CommonSessionInterfacingFunctionality { var shooting: mutable.Set[PlanetSideGUID] = mutable.Set.empty //ChangeFireStateMessage_Start var prefire: mutable.Set[PlanetSideGUID] = mutable.Set.empty //if WeaponFireMessage precedes ChangeFireStateMessage_Start + private[session] var orbitalStrikePos: Option[Vector3] = None private[session] var shootingStart: mutable.HashMap[PlanetSideGUID, Long] = mutable.HashMap[PlanetSideGUID, Long]() private[session] var shootingStop: mutable.HashMap[PlanetSideGUID, Long] = mutable.HashMap[PlanetSideGUID, Long]() private[session] val shotsFired: mutable.HashMap[Int,Int] = mutable.HashMap[Int,Int]() @@ -292,6 +297,79 @@ class WeaponAndProjectileOperations( } } + def handleUplinkRequest(pkt: UplinkRequest): Unit = { + val UplinkRequest(code, pos, _) = pkt + val playerFaction = player.Faction + code match { + case UplinkRequestType.RevealFriendlies => () + /*sendResponse(UplinkResponse(code.value, 0)) + sendResponse(PlanetsideAttributeMessage(player.GUID, 57, 10000)) //1200000 + avatarActor ! AvatarActor.UpdateCUDTime("reveal_friendlies")*/ + case UplinkRequestType.RevealEnemies => () + /*sendResponse(UplinkResponse(code.value, 0)) + sendResponse(PlanetsideAttributeMessage(player.GUID, 58, 10000)) //1200000 + avatarActor ! AvatarActor.UpdateCUDTime("reveal_enemies") + These are seen in a few logs, but didn't work. Unclear what these do or what '10' in Event1 represents + sendResponse(UplinkPositionEvent(5, Event0(5))) + sendResponse(UplinkPositionEvent(4, Event1(4, 10))) + sendResponse(UplinkPositionEvent(6, Event0(6)))*/ + case UplinkRequestType.ElectroMagneticPulse => + val cr = player.avatar.cr.value + val empSize = cr match { + case 3 => cr3_emp + case 4 => cr4_emp + case 5 => cr5_emp + } + val empColor = if (playerFaction != PlanetSideEmpire.NEUTRAL) { s"explosion_emp_${playerFaction.toString.toLowerCase}" } else { "explosion_emp_bo" } + sendResponse(UplinkResponse(code.value, 0)) + sendResponse(PlanetsideAttributeMessage(player.GUID, 59, 10000)) //1200000 + avatarActor ! AvatarActor.UpdateCUDTime("emp_blast") + player.Zone.LocalEvents ! LocalServiceMessage(s"${player.Zone.id}", + LocalAction.SendPacket(TriggerEffectMessage(ValidPlanetSideGUID(0), empColor, None, Some(TriggeredEffectLocation(player.Position, Vector3(0, 0, 90)))))) + context.system.scheduler.scheduleOnce(delay = 1 seconds) { + Zone.serverSideDamage(player.Zone, player, empSize, SpecialEmp.createEmpInteraction(empSize, player.Position), + ExplosiveDeployableControl.detectionForExplosiveSource(player), Zone.findAllTargets) + } + case UplinkRequestType.OrbitalStrike => + player.Zone.LocalEvents ! LocalServiceMessage(s"$playerFaction", LocalAction.SendPacket(OrbitalStrikeWaypointMessage(player.GUID, pos.get.x, pos.get.y))) + sendResponse(UplinkResponse(code.value, 0)) + orbitalStrikePos = pos + case UplinkRequestType.Unknown5 => + val cr = player.avatar.cr.value + val strikeType = playerFaction match { + case PlanetSideEmpire.NC => + if (cr == 4) {"explosion_bluedeath_nc"} else {"explosion_bluedeath_nc_lrg"} + case PlanetSideEmpire.TR => + if (cr == 4) {"explosion_bluedeath_tr"} else {"explosion_bluedeath_tr_lrg"} + case PlanetSideEmpire.VS => + if (cr == 4) {"explosion_bluedeath_vs"} else {"explosion_bluedeath_vs_lrg"} + case PlanetSideEmpire.NEUTRAL => + if (cr == 4) {"explosion_bluedeath_bo"} else {"explosion_bluedeath_bo_lrg"} + } + val osSize = cr match { + case 4 => cr4_os + case 5 => cr5_os + } + sendResponse(UplinkResponse(code.value, 0)) + sendResponse(PlanetsideAttributeMessage(player.GUID, 60, 10000)) //10800000 + avatarActor ! AvatarActor.UpdateCUDTime("orbital_strike") + context.system.scheduler.scheduleOnce(delay = 5 seconds) { + player.Zone.LocalEvents ! LocalServiceMessage(s"${player.Zone.id}", + LocalAction.SendPacket(TriggerEffectMessage(ValidPlanetSideGUID(0), strikeType, None, Some(TriggeredEffectLocation(orbitalStrikePos.get, Vector3(0, 0, 90)))))) + player.Zone.LocalEvents ! LocalServiceMessage(s"$playerFaction", LocalAction.SendPacket(OrbitalStrikeWaypointMessage(player.GUID, None))) + context.system.scheduler.scheduleOnce(delay = 5 seconds) { + val sectorTargets = Zone.findOrbitalStrikeTargets(player.Zone, orbitalStrikePos.get, osSize.DamageRadius, Zone.getOrbitbalStrikeTargets) + val withinRange = sectorTargets.filter {target => Zone.orbitalStrikeDistanceCheck(orbitalStrikePos.get, target.Position, osSize.DamageRadius)} + withinRange.foreach { target => + target.Actor ! Vitality.Damage(DamageInteraction(SourceEntry(target), OrbitalStrike(PlayerSource(player)), target.Position).calculate()) + } + orbitalStrikePos = None + } + } + case _ => () + } + } + def handleChangeAmmo(pkt: ChangeAmmoMessage): Unit = { val ChangeAmmoMessage(item_guid, _) = pkt val (thing, equipment) = sessionLogic.findContainedEquipment() 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 a60ac1edc..9e62d70ee 100644 --- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala @@ -2613,7 +2613,6 @@ class ZoningOperations( sendResponse(ObjectCreateDetailedMessage(ObjectClass.avatar, guid, data)) log.debug(s"AvatarRejoin: ${player.Name} - $guid -> $data") } - avatarActor ! AvatarActor.RefreshPurchaseTimes() setupAvatarFunc = AvatarCreate //begin looking for conditions to set the avatar context.system.scheduler.scheduleOnce(delay = 750 millisecond, context.self, SessionActor.SetCurrentAvatar(player, 200)) @@ -3276,6 +3275,7 @@ class ZoningOperations( ZoningOperations.reportProgressionSystem(context.self) } } + avatarActor ! AvatarActor.RefreshPurchaseTimes() } /** diff --git a/src/main/scala/net/psforever/objects/OrbitalStrike.scala b/src/main/scala/net/psforever/objects/OrbitalStrike.scala new file mode 100644 index 000000000..33634d852 --- /dev/null +++ b/src/main/scala/net/psforever/objects/OrbitalStrike.scala @@ -0,0 +1,61 @@ +// Copyright (c) 2025 PSForever +package net.psforever.objects + +import net.psforever.objects.sourcing.{PlayerSource, SourceEntry} +import net.psforever.objects.vital.{NoResistanceSelection, SimpleResolutions} +import net.psforever.objects.vital.base.{DamageReason, DamageResolution, DamageType} +import net.psforever.objects.vital.damage.DamageCalculations +import net.psforever.objects.vital.prop.{DamageProperties, DamageWithPosition} +import net.psforever.objects.vital.resolution.{DamageAndResistance, DamageResistanceModel} + +final case class OrbitalStrike(player: PlayerSource) + extends DamageReason { + def resolution: DamageResolution.Value = DamageResolution.Hit + + def same(test: DamageReason): Boolean = { + test.source eq source + } + + def source: DamageProperties = OrbitalStrike.source + + def damageModel: DamageAndResistance = OrbitalStrike.drm + + override def adversary : Option[SourceEntry] = Some(player) + +} + +object OrbitalStrike { + + final val cr4_os = new DamageWithPosition { + CausesDamageType = DamageType.Splash + SympatheticExplosion = true + Damage0 = 10000 + DamageAtEdge = 0.1f + DamageRadius = 10f + } + + final val cr5_os = new DamageWithPosition { + CausesDamageType = DamageType.Splash + SympatheticExplosion = true + Damage0 = 10000 + DamageAtEdge = 0.1f + DamageRadius = 20f + } + + private val source = new DamageProperties { + Damage0 = 10000 + Damage1 = 10000 + Damage2 = 10000 + Damage3 = 10000 + Damage4 = 10000 + DamageToHealthOnly = true + DamageToVehicleOnly = true + DamageToBattleframeOnly = true + } + + private val drm = new DamageResistanceModel { + DamageUsing = DamageCalculations.AgainstExoSuit + ResistUsing = NoResistanceSelection + Model = SimpleResolutions.calculate + } +} diff --git a/src/main/scala/net/psforever/objects/SpecialEmp.scala b/src/main/scala/net/psforever/objects/SpecialEmp.scala index 5578690af..b58a0e377 100644 --- a/src/main/scala/net/psforever/objects/SpecialEmp.scala +++ b/src/main/scala/net/psforever/objects/SpecialEmp.scala @@ -65,6 +65,108 @@ object SpecialEmp { innateDamage = emp } + final val cr3_emp = new DamageWithPosition { + CausesDamageType = DamageType.Splash + SympatheticExplosion = true + Damage0 = 0 + DamageAtEdge = 1.0f + DamageRadius = 10f + AdditionalEffect = true + JammedEffectDuration += TargetValidation( + EffectTarget.Category.Player, + EffectTarget.Validation.Player + ) -> 1000 + JammedEffectDuration += TargetValidation( + EffectTarget.Category.Vehicle, + EffectTarget.Validation.AMS + ) -> 5000 + JammedEffectDuration += TargetValidation( + EffectTarget.Category.Deployable, + EffectTarget.Validation.MotionSensor + ) -> 30000 + JammedEffectDuration += TargetValidation( + EffectTarget.Category.Deployable, + EffectTarget.Validation.Spitfire + ) -> 30000 + JammedEffectDuration += TargetValidation( + EffectTarget.Category.Turret, + EffectTarget.Validation.Turret + ) -> 30000 + JammedEffectDuration += TargetValidation( + EffectTarget.Category.Vehicle, + EffectTarget.Validation.VehicleNotAMS + ) -> 10000 + Modifiers = MaxDistanceCutoff + } + + final val cr4_emp = new DamageWithPosition { + CausesDamageType = DamageType.Splash + SympatheticExplosion = true + Damage0 = 0 + DamageAtEdge = 1.0f + DamageRadius = 15f + AdditionalEffect = true + JammedEffectDuration += TargetValidation( + EffectTarget.Category.Player, + EffectTarget.Validation.Player + ) -> 1000 + JammedEffectDuration += TargetValidation( + EffectTarget.Category.Vehicle, + EffectTarget.Validation.AMS + ) -> 5000 + JammedEffectDuration += TargetValidation( + EffectTarget.Category.Deployable, + EffectTarget.Validation.MotionSensor + ) -> 30000 + JammedEffectDuration += TargetValidation( + EffectTarget.Category.Deployable, + EffectTarget.Validation.Spitfire + ) -> 30000 + JammedEffectDuration += TargetValidation( + EffectTarget.Category.Turret, + EffectTarget.Validation.Turret + ) -> 30000 + JammedEffectDuration += TargetValidation( + EffectTarget.Category.Vehicle, + EffectTarget.Validation.VehicleNotAMS + ) -> 10000 + Modifiers = MaxDistanceCutoff + } + + final val cr5_emp = new DamageWithPosition { + CausesDamageType = DamageType.Splash + SympatheticExplosion = true + Damage0 = 0 + DamageAtEdge = 1.0f + DamageRadius = 20f + AdditionalEffect = true + JammedEffectDuration += TargetValidation( + EffectTarget.Category.Player, + EffectTarget.Validation.Player + ) -> 1000 + JammedEffectDuration += TargetValidation( + EffectTarget.Category.Vehicle, + EffectTarget.Validation.AMS + ) -> 5000 + JammedEffectDuration += TargetValidation( + EffectTarget.Category.Deployable, + EffectTarget.Validation.MotionSensor + ) -> 30000 + JammedEffectDuration += TargetValidation( + EffectTarget.Category.Deployable, + EffectTarget.Validation.Spitfire + ) -> 30000 + JammedEffectDuration += TargetValidation( + EffectTarget.Category.Turret, + EffectTarget.Validation.Turret + ) -> 30000 + JammedEffectDuration += TargetValidation( + EffectTarget.Category.Vehicle, + EffectTarget.Validation.VehicleNotAMS + ) -> 10000 + Modifiers = MaxDistanceCutoff + } + /** * Trigger an electromagnetic pulse. */ diff --git a/src/main/scala/net/psforever/objects/avatar/Avatar.scala b/src/main/scala/net/psforever/objects/avatar/Avatar.scala index 4e349ec55..8f0a41a16 100644 --- a/src/main/scala/net/psforever/objects/avatar/Avatar.scala +++ b/src/main/scala/net/psforever/objects/avatar/Avatar.scala @@ -68,6 +68,13 @@ object Avatar { GlobalDefinitions.trhev_pounder -> 5.minutes ) + val cudCooldowns: Map[String, FiniteDuration] = Map( + "orbital_strike" -> 1.minutes, // 3.hours + "emp_blast" -> 1.minutes, // 20.minutes + "reveal_friendlies" -> 1.minutes, // 20.minutes + "reveal_enemies" -> 1.minutes // 20.minutes + ) + val useCooldowns: Map[BasicDefinition, FiniteDuration] = Map( GlobalDefinitions.medkit -> 5.seconds, GlobalDefinitions.super_armorkit -> 20.minutes, diff --git a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala index 375f4cec5..4e151f973 100644 --- a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala +++ b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala @@ -36,6 +36,7 @@ import net.psforever.objects.vital.collision.CollisionReason import net.psforever.objects.vital.etc.{PainboxReason, SuicideReason} import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult} import net.psforever.packet.PlanetSideGamePacket +import org.joda.time.{LocalDateTime, Seconds} import scala.concurrent.duration._ @@ -359,6 +360,11 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm equipment match { case Some(holsteredEquipment) => log.info(s"${player.Name} has put ${player.Sex.possessive} ${holsteredEquipment.Definition.Name} down") + //make sure the player didn't just initialte an orbital strike. If not (the if below is true), make sure waypoint is removed + if (holsteredEquipment.Definition == GlobalDefinitions.command_detonater && player.avatar.cr.value > 3 && + !player.avatar.cooldowns.purchase.exists(os => os._1 == "orbital_strike" && Seconds.secondsBetween(os._2, LocalDateTime.now()).getSeconds < 10)) { + player.Zone.LocalEvents ! LocalServiceMessage(s"${player.Faction}", LocalAction.SendPacket(OrbitalStrikeWaypointMessage(player.GUID, None))) + } case None => log.info(s"${player.Name} lowers ${player.Sex.possessive} hand") } diff --git a/src/main/scala/net/psforever/objects/zones/Zone.scala b/src/main/scala/net/psforever/objects/zones/Zone.scala index 45eb69a12..cbf828d07 100644 --- a/src/main/scala/net/psforever/objects/zones/Zone.scala +++ b/src/main/scala/net/psforever/objects/zones/Zone.scala @@ -1845,6 +1845,53 @@ object Zone { playerTargets ++ vehicleTargets ++ deployableTargets ++ soiTargets } + /** + * na + * @see `DamageWithPosition` + * @see `Zone.blockMap.sector` + * @param zone the zone in which the explosion should occur + * @param sourcePosition a position that is used as the origin of the explosion + * @param radius idistance + * @param getTargetsFromSector get this list of entities from a sector + * @return a list of affected entities + */ + def findOrbitalStrikeTargets( + zone: Zone, + sourcePosition: Vector3, + radius: Float, + getTargetsFromSector: SectorPopulation => List[PlanetSideServerObject with Vitality] + ): List[PlanetSideServerObject with Vitality] = { + getTargetsFromSector(zone.blockMap.sector(sourcePosition.xy, radius)) + } + + def getOrbitbalStrikeTargets(sector: SectorPopulation): List[PlanetSideServerObject with Vitality] = { + //collect all targets that can be damaged + //players + val playerTargets = sector.livePlayerList.filter { player => player.VehicleSeated.isEmpty && player.WhichSide == Sidedness.OutsideOf } + //vehicles + val vehicleTargets = sector.vehicleList.filterNot { _.Destroyed } + //deployables + val deployableTargets = sector.deployableList.filter { obj => !obj.Destroyed && obj.WhichSide == Sidedness.OutsideOf } + //amenities + val soiTargets = sector.amenityList.collect { + case amenity: Vitality if !amenity.Destroyed && amenity.WhichSide == Sidedness.OutsideOf && amenity.CanDamage => amenity } + //altogether ... + playerTargets ++ vehicleTargets ++ deployableTargets ++ soiTargets + } + + /** + * Check if targets returned from sector are within range of the imminent Orbital Strike + * @param p1 OS position + * @param p2 target position + * @param maxDistance radius of the Orbital Strike + * @return `true`, if the two entities are near enough to each other; + * `false`, otherwise + */ + def orbitalStrikeDistanceCheck(p1: Vector3, p2: Vector3, maxDistance: Float): Boolean = { + val radius = maxDistance * maxDistance + Vector3.DistanceSquared(p1.xy, p2.xy) <= radius + } + /** * na * @param instigation what previous event happened, if any, that caused this explosion diff --git a/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala b/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala index aaf908d14..87f94fb5f 100644 --- a/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala +++ b/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala @@ -162,6 +162,10 @@ import scodec.codecs._ * * `55 - "Someone is attempting to Heal you". Value is 1`
* `56 - "Someone is attempting to Repair you". Value is 1`
+ * `57 - CUD Reveal Friendlies cooldown timer`
+ * `58 - CUD Reveal Enemies cooldown timer`
+ * `59 - CUD EMP Blast cooldown timer`
+ * `60 - CUD Orbital Strike cooldown timer`
* `64 - ????? related to using router telepads` * `67 - Enables base shields (from cavern module/lock)`
* `73 - "You are locked into the Core Beam. Charging your Module now.". Value is 1 to active`
diff --git a/src/main/scala/net/psforever/packet/game/UplinkRequest.scala b/src/main/scala/net/psforever/packet/game/UplinkRequest.scala index f4a66e591..2d4197269 100644 --- a/src/main/scala/net/psforever/packet/game/UplinkRequest.scala +++ b/src/main/scala/net/psforever/packet/game/UplinkRequest.scala @@ -26,15 +26,15 @@ object UplinkRequestType extends IntEnum[UplinkRequestType] { case object OrbitalStrike extends UplinkRequestType(value = 4) - case object Unknown5 extends UplinkRequestType(value = 5) + case object Unknown5 extends UplinkRequestType(value = 5) // pull trigger to start orbital strike countdown case object Function6 extends UplinkRequestType(value = 6) - case object Function7 extends UplinkRequestType(value = 7) + case object Function7 extends UplinkRequestType(value = 7) // sent back by client after reveal enemies response case object Function8 extends UplinkRequestType(value = 8) - case object Unknown9 extends UplinkRequestType(value = 9) + case object Unknown9 extends UplinkRequestType(value = 9) // recall squad to sanc case object UnknownA extends UplinkRequestType(value = 10) From c9b13394e9fdd8f980e476edbd1237759633caf1 Mon Sep 17 00:00:00 2001 From: ScrawnyRonnie Date: Thu, 17 Jul 2025 20:19:49 -0400 Subject: [PATCH 06/19] just one os please --- .../support/WeaponAndProjectileOperations.scala | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala index eebb56466..7dba959fc 100644 --- a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala @@ -94,6 +94,7 @@ class WeaponAndProjectileOperations( var shooting: mutable.Set[PlanetSideGUID] = mutable.Set.empty //ChangeFireStateMessage_Start var prefire: mutable.Set[PlanetSideGUID] = mutable.Set.empty //if WeaponFireMessage precedes ChangeFireStateMessage_Start private[session] var orbitalStrikePos: Option[Vector3] = None + private[session] var orbitalStrikeInProgress: Boolean = false private[session] var shootingStart: mutable.HashMap[PlanetSideGUID, Long] = mutable.HashMap[PlanetSideGUID, Long]() private[session] var shootingStop: mutable.HashMap[PlanetSideGUID, Long] = mutable.HashMap[PlanetSideGUID, Long]() private[session] val shotsFired: mutable.HashMap[Int,Int] = mutable.HashMap[Int,Int]() @@ -335,6 +336,8 @@ class WeaponAndProjectileOperations( sendResponse(UplinkResponse(code.value, 0)) orbitalStrikePos = pos case UplinkRequestType.Unknown5 => + if (!orbitalStrikeInProgress) { + orbitalStrikeInProgress = true val cr = player.avatar.cr.value val strikeType = playerFaction match { case PlanetSideEmpire.NC => @@ -359,13 +362,18 @@ class WeaponAndProjectileOperations( player.Zone.LocalEvents ! LocalServiceMessage(s"$playerFaction", LocalAction.SendPacket(OrbitalStrikeWaypointMessage(player.GUID, None))) context.system.scheduler.scheduleOnce(delay = 5 seconds) { val sectorTargets = Zone.findOrbitalStrikeTargets(player.Zone, orbitalStrikePos.get, osSize.DamageRadius, Zone.getOrbitbalStrikeTargets) - val withinRange = sectorTargets.filter {target => Zone.orbitalStrikeDistanceCheck(orbitalStrikePos.get, target.Position, osSize.DamageRadius)} + val withinRange = sectorTargets.filter { target => Zone.orbitalStrikeDistanceCheck(orbitalStrikePos.get, target.Position, osSize.DamageRadius) } withinRange.foreach { target => target.Actor ! Vitality.Damage(DamageInteraction(SourceEntry(target), OrbitalStrike(PlayerSource(player)), target.Position).calculate()) } orbitalStrikePos = None + orbitalStrikeInProgress = false + } } } + else { + sendResponse(UplinkResponse(code.value, 0)) + } case _ => () } } From 1b6e50cf0d06ecdbf07b003ccdbba7ec7bb4cfa0 Mon Sep 17 00:00:00 2001 From: ScrawnyRonnie Date: Fri, 18 Jul 2025 21:29:42 -0400 Subject: [PATCH 07/19] we can reveal --- .../WeaponAndProjectileOperations.scala | 50 +++++++++++++------ .../net/psforever/objects/avatar/Avatar.scala | 8 +-- .../objects/definition/BasicDefinition.scala | 8 +++ .../global/GlobalDefinitionsVehicle.scala | 39 +++++++++++++++ 4 files changed, 87 insertions(+), 18 deletions(-) diff --git a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala index 7dba959fc..24ba6f335 100644 --- a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala @@ -302,18 +302,40 @@ class WeaponAndProjectileOperations( val UplinkRequest(code, pos, _) = pkt val playerFaction = player.Faction code match { - case UplinkRequestType.RevealFriendlies => () - /*sendResponse(UplinkResponse(code.value, 0)) - sendResponse(PlanetsideAttributeMessage(player.GUID, 57, 10000)) //1200000 - avatarActor ! AvatarActor.UpdateCUDTime("reveal_friendlies")*/ - case UplinkRequestType.RevealEnemies => () - /*sendResponse(UplinkResponse(code.value, 0)) - sendResponse(PlanetsideAttributeMessage(player.GUID, 58, 10000)) //1200000 - avatarActor ! AvatarActor.UpdateCUDTime("reveal_enemies") - These are seen in a few logs, but didn't work. Unclear what these do or what '10' in Event1 represents - sendResponse(UplinkPositionEvent(5, Event0(5))) - sendResponse(UplinkPositionEvent(4, Event1(4, 10))) - sendResponse(UplinkPositionEvent(6, Event0(6)))*/ + case UplinkRequestType.RevealFriendlies => + val revealZone = player.Zone.Number + sendResponse(UplinkResponse(code.value, 0)) + sendResponse(PlanetsideAttributeMessage(player.GUID, 57, 1200000)) + avatarActor ! AvatarActor.UpdateCUDTime("reveal_friendlies") + sendResponse(UplinkPositionEvent(5, Event0(5))) + sendResponse(UplinkPositionEvent(4, Event1(4, revealZone))) + sendResponse(UplinkPositionEvent(6, Event0(6))) + val friendlies = player.Zone.LivePlayers.filter { friend => friend.Faction == player.Faction } + val friendlyVehicles = player.Zone.Vehicles.filter { vehicle => vehicle.Faction == player.Faction && !vehicle.Destroyed } + friendlies.foreach { f => + sendResponse(UplinkPositionEvent(0, Event2(0, Vector3(f.Position.x, f.Position.y, 0.0f), 255, revealZone, 0, 1118938442, 300000, 299080, Some(true)))) + } + friendlyVehicles.foreach { v => + sendResponse(UplinkPositionEvent(0, Event2(0, Vector3(v.Position.x, v.Position.y, 0.0f), v.Definition.MapRevealId, revealZone, 0, 1118938442, 300000, 299080, Some(true)))) + } + case UplinkRequestType.RevealEnemies => + val revealZone = player.Zone.Number + sendResponse(UplinkResponse(code.value, 0)) + sendResponse(PlanetsideAttributeMessage(player.GUID, 58, 1200000)) + avatarActor ! AvatarActor.UpdateCUDTime("reveal_enemies") + sendResponse(UplinkPositionEvent(5, Event0(5))) + sendResponse(UplinkPositionEvent(4, Event1(4, revealZone))) + sendResponse(UplinkPositionEvent(6, Event0(6))) + val enemies = player.Zone.LivePlayers.filter { enemy => enemy.Faction != player.Faction && + Zone.orbitalStrikeDistanceCheck(player.Position, enemy.Position, 200f)} //reusing distance check + val enemyVehicles = player.Zone.Vehicles.filter { vehicle => vehicle.Faction != player.Faction && !vehicle.Destroyed && + Zone.orbitalStrikeDistanceCheck(player.Position, vehicle.Position, 200f)} //reusing distance check + enemies.foreach { e => + sendResponse(UplinkPositionEvent(1, Event2(1, Vector3(e.Position.x, e.Position.y, 0.0f), 255, revealZone, 0, 1118938442, 300000, 299080, Some(false)))) + } + enemyVehicles.foreach { v => + sendResponse(UplinkPositionEvent(1, Event2(1, Vector3(v.Position.x, v.Position.y, 0.0f), v.Definition.MapRevealId, revealZone, 0, 1118938442, 300000, 299080, Some(false)))) + } case UplinkRequestType.ElectroMagneticPulse => val cr = player.avatar.cr.value val empSize = cr match { @@ -323,7 +345,7 @@ class WeaponAndProjectileOperations( } val empColor = if (playerFaction != PlanetSideEmpire.NEUTRAL) { s"explosion_emp_${playerFaction.toString.toLowerCase}" } else { "explosion_emp_bo" } sendResponse(UplinkResponse(code.value, 0)) - sendResponse(PlanetsideAttributeMessage(player.GUID, 59, 10000)) //1200000 + sendResponse(PlanetsideAttributeMessage(player.GUID, 59, 1200000)) avatarActor ! AvatarActor.UpdateCUDTime("emp_blast") player.Zone.LocalEvents ! LocalServiceMessage(s"${player.Zone.id}", LocalAction.SendPacket(TriggerEffectMessage(ValidPlanetSideGUID(0), empColor, None, Some(TriggeredEffectLocation(player.Position, Vector3(0, 0, 90)))))) @@ -354,7 +376,7 @@ class WeaponAndProjectileOperations( case 5 => cr5_os } sendResponse(UplinkResponse(code.value, 0)) - sendResponse(PlanetsideAttributeMessage(player.GUID, 60, 10000)) //10800000 + sendResponse(PlanetsideAttributeMessage(player.GUID, 60, 10800000)) avatarActor ! AvatarActor.UpdateCUDTime("orbital_strike") context.system.scheduler.scheduleOnce(delay = 5 seconds) { player.Zone.LocalEvents ! LocalServiceMessage(s"${player.Zone.id}", diff --git a/src/main/scala/net/psforever/objects/avatar/Avatar.scala b/src/main/scala/net/psforever/objects/avatar/Avatar.scala index 8f0a41a16..633943e26 100644 --- a/src/main/scala/net/psforever/objects/avatar/Avatar.scala +++ b/src/main/scala/net/psforever/objects/avatar/Avatar.scala @@ -69,10 +69,10 @@ object Avatar { ) val cudCooldowns: Map[String, FiniteDuration] = Map( - "orbital_strike" -> 1.minutes, // 3.hours - "emp_blast" -> 1.minutes, // 20.minutes - "reveal_friendlies" -> 1.minutes, // 20.minutes - "reveal_enemies" -> 1.minutes // 20.minutes + "orbital_strike" -> 3.hours, + "emp_blast" -> 20.minutes, + "reveal_friendlies" -> 20.minutes, + "reveal_enemies" -> 20.minutes ) val useCooldowns: Map[BasicDefinition, FiniteDuration] = Map( diff --git a/src/main/scala/net/psforever/objects/definition/BasicDefinition.scala b/src/main/scala/net/psforever/objects/definition/BasicDefinition.scala index 092d763a6..9649420f2 100644 --- a/src/main/scala/net/psforever/objects/definition/BasicDefinition.scala +++ b/src/main/scala/net/psforever/objects/definition/BasicDefinition.scala @@ -4,6 +4,7 @@ package net.psforever.objects.definition abstract class BasicDefinition { private var name: String = "definition" private var descriptor: Option[String] = None + private var mapRevealId: Int = 0 def Name: String = name @@ -20,4 +21,11 @@ abstract class BasicDefinition { descriptor = description Descriptor } + + def MapRevealId: Int = mapRevealId //for vehicle IDs used by reveal friendlies and enemies with a CUD + + def MapRevealId_=(mapRevealId: Int): Int = { + this.mapRevealId = mapRevealId + MapRevealId + } } diff --git a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsVehicle.scala b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsVehicle.scala index 5e7ae6dad..c2fee38f6 100644 --- a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsVehicle.scala +++ b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsVehicle.scala @@ -83,6 +83,7 @@ object GlobalDefinitionsVehicle { fury.collision.z = CollisionZData(Array((8f, 1), (24f, 35), (40f, 100), (48f, 175), (52f, 350))) fury.maxForwardSpeed = 90f fury.mass = 32.1f + fury.MapRevealId = 14 quadassault.Name = "quadassault" // Basilisk quadassault.MaxHealth = 650 @@ -119,6 +120,7 @@ object GlobalDefinitionsVehicle { quadassault.collision.z = CollisionZData(Array((8f, 1), (24f, 35), (40f, 100), (48f, 175), (52f, 350))) quadassault.maxForwardSpeed = 90f quadassault.mass = 32.1f + quadassault.MapRevealId = 30 quadstealth.Name = "quadstealth" // Wraith quadstealth.MaxHealth = 650 @@ -155,6 +157,7 @@ object GlobalDefinitionsVehicle { quadstealth.collision.z = CollisionZData(Array((8f, 1), (24f, 35), (40f, 100), (48f, 175), (52f, 350))) quadstealth.maxForwardSpeed = 90f quadstealth.mass = 32.1f + quadstealth.MapRevealId = 15 two_man_assault_buggy.Name = "two_man_assault_buggy" // Harasser two_man_assault_buggy.MaxHealth = 1250 @@ -193,6 +196,7 @@ object GlobalDefinitionsVehicle { two_man_assault_buggy.collision.z = CollisionZData(Array((7f, 1), (21f, 50), (35f, 150), (42f, 300), (45.5f, 600))) two_man_assault_buggy.maxForwardSpeed = 85f two_man_assault_buggy.mass = 52.4f + two_man_assault_buggy.MapRevealId = 1 skyguard.Name = "skyguard" skyguard.MaxHealth = 1000 @@ -232,6 +236,7 @@ object GlobalDefinitionsVehicle { skyguard.collision.z = CollisionZData(Array((7f, 1), (21f, 50), (35f, 150), (42f, 300), (45.4f, 600))) skyguard.maxForwardSpeed = 90f skyguard.mass = 78.9f + skyguard.MapRevealId = 16 threemanheavybuggy.Name = "threemanheavybuggy" // Marauder threemanheavybuggy.MaxHealth = 1700 @@ -274,6 +279,7 @@ object GlobalDefinitionsVehicle { threemanheavybuggy.collision.z = CollisionZData(Array((6f, 1), (18f, 50), (30f, 150), (36f, 300), (39f, 900))) threemanheavybuggy.maxForwardSpeed = 80f threemanheavybuggy.mass = 96.3f + threemanheavybuggy.MapRevealId = 18 twomanheavybuggy.Name = "twomanheavybuggy" // Enforcer twomanheavybuggy.MaxHealth = 1800 @@ -313,6 +319,7 @@ object GlobalDefinitionsVehicle { twomanheavybuggy.collision.z = CollisionZData(Array((6f, 1), (18f, 50), (30f, 150), (36f, 300), (39f, 900))) twomanheavybuggy.maxForwardSpeed = 80f twomanheavybuggy.mass = 83.2f + twomanhoverbuggy.MapRevealId = 20 twomanhoverbuggy.Name = "twomanhoverbuggy" // Thresher twomanhoverbuggy.MaxHealth = 1600 @@ -355,6 +362,7 @@ object GlobalDefinitionsVehicle { twomanhoverbuggy.collision.z = CollisionZData(Array((6f, 1), (18f, 50), (30f, 150), (36f, 300), (39f, 900))) twomanhoverbuggy.maxForwardSpeed = 85f twomanhoverbuggy.mass = 55.5f + twomanhoverbuggy.MapRevealId = 21 mediumtransport.Name = "mediumtransport" // Deliverer mediumtransport.MaxHealth = 2500 @@ -401,6 +409,7 @@ object GlobalDefinitionsVehicle { mediumtransport.collision.z = CollisionZData(Array((5f, 1), (15f, 50), (25f, 200), (30f, 750), (32.5f, 2000))) mediumtransport.maxForwardSpeed = 70f mediumtransport.mass = 108.5f + mediumtransport.MapRevealId = 10 battlewagon.Name = "battlewagon" // Raider battlewagon.MaxHealth = 2500 @@ -450,6 +459,7 @@ object GlobalDefinitionsVehicle { battlewagon.collision.z = CollisionZData(Array((5f, 1), (15f, 50), (25f, 200), (30f, 750), (32.5f, 2000))) //inherited from mediumtransport battlewagon.maxForwardSpeed = 65f battlewagon.mass = 108.5f + battlewagon.MapRevealId = 10 thunderer.Name = "thunderer" thunderer.MaxHealth = 2500 @@ -496,6 +506,7 @@ object GlobalDefinitionsVehicle { thunderer.collision.z = CollisionZData(Array((5f, 1), (15f, 50), (25f, 200), (30f, 750), (32.5f, 2000))) thunderer.maxForwardSpeed = 65f thunderer.mass = 108.5f + thunderer.MapRevealId = 29 aurora.Name = "aurora" aurora.MaxHealth = 2500 @@ -542,6 +553,7 @@ object GlobalDefinitionsVehicle { aurora.collision.z = CollisionZData(Array((5f, 1), (15f, 50), (25f, 200), (30f, 750), (32.5f, 2000))) aurora.maxForwardSpeed = 65f aurora.mass = 108.5f + aurora.MapRevealId = 28 apc_tr.Name = "apc_tr" // Juggernaut apc_tr.MaxHealth = 6000 @@ -612,6 +624,7 @@ object GlobalDefinitionsVehicle { apc_tr.collision.z = CollisionZData(Array((2f, 1), (6f, 50), (10f, 300), (12f, 1000), (13f, 3000))) apc_tr.maxForwardSpeed = 60f apc_tr.mass = 128.4f + apc_tr.MapRevealId = 32 apc_nc.Name = "apc_nc" // Vindicator apc_nc.MaxHealth = 6000 @@ -682,6 +695,7 @@ object GlobalDefinitionsVehicle { apc_nc.collision.z = CollisionZData(Array((2f, 1), (6f, 50), (10f, 300), (12f, 1000), (13f, 3000))) apc_nc.maxForwardSpeed = 60f apc_nc.mass = 128.4f + apc_nc.MapRevealId = 33 apc_vs.Name = "apc_vs" // Leviathan apc_vs.MaxHealth = 6000 @@ -752,6 +766,7 @@ object GlobalDefinitionsVehicle { apc_vs.collision.z = CollisionZData(Array((2f, 1), (6f, 50), (10f, 300), (12f, 1000), (13f, 3000))) apc_vs.maxForwardSpeed = 60f apc_vs.mass = 128.4f + apc_vs.MapRevealId = 41 lightning.Name = "lightning" lightning.MaxHealth = 2000 @@ -790,6 +805,7 @@ object GlobalDefinitionsVehicle { lightning.collision.z = CollisionZData(Array((6f, 1), (18f, 50), (30f, 150), (36f, 300), (39f, 750))) lightning.maxForwardSpeed = 74f lightning.mass = 100.2f + lightning.MapRevealId = 7 prowler.Name = "prowler" prowler.MaxHealth = 4800 @@ -833,6 +849,7 @@ object GlobalDefinitionsVehicle { prowler.collision.z = CollisionZData(Array((5f, 1), (15f, 50), (25f, 250), (30f, 600), (32.5f, 1500))) prowler.maxForwardSpeed = 57f prowler.mass = 510.5f + prowler.MapRevealId = 13 vanguard.Name = "vanguard" vanguard.MaxHealth = 5400 @@ -872,6 +889,7 @@ object GlobalDefinitionsVehicle { vanguard.collision.z = CollisionZData(Array((5f, 1), (15f, 50), (25f, 100), (30f, 250), (32.5f, 600))) vanguard.maxForwardSpeed = 60f vanguard.mass = 460.4f + vanguard.MapRevealId = 22 magrider.Name = "magrider" magrider.MaxHealth = 4200 @@ -913,6 +931,7 @@ object GlobalDefinitionsVehicle { magrider.collision.z = CollisionZData(Array((5f, 1), (15f, 50), (25f, 250), (30f, 600), (32.5f, 1500))) magrider.maxForwardSpeed = 65f magrider.mass = 75.3f + magrider.MapRevealId = 8 val utilityConverter = new UtilityVehicleConverter ant.Name = "ant" @@ -952,6 +971,7 @@ object GlobalDefinitionsVehicle { ant.collision.z = CollisionZData(Array((2f, 1), (6f, 50), (10f, 250), (12f, 500), (13f, 750))) ant.maxForwardSpeed = 65f ant.mass = 80.5f + ant.MapRevealId = 1 ams.Name = "ams" ams.MaxHealth = 3000 @@ -995,6 +1015,7 @@ object GlobalDefinitionsVehicle { ams.collision.z = CollisionZData(Array((2f, 1), (6f, 50), (10f, 250), (12f, 805), (13f, 3000))) ams.maxForwardSpeed = 70f ams.mass = 136.8f + ams.MapRevealId = 2 val variantConverter = new VariantVehicleConverter router.Name = "router" @@ -1039,6 +1060,7 @@ object GlobalDefinitionsVehicle { router.collision.z = CollisionZData(Array((6f, 1), (18f, 50), (30f, 150), (36f, 350), (39f, 900))) router.maxForwardSpeed = 60f router.mass = 60f + router.MapRevealId = 25 switchblade.Name = "switchblade" switchblade.MaxHealth = 1750 @@ -1085,6 +1107,7 @@ object GlobalDefinitionsVehicle { switchblade.collision.z = CollisionZData(Array((6f, 1), (18f, 50), (30f, 150), (36f, 350), (39f, 800))) switchblade.maxForwardSpeed = 80f switchblade.mass = 63.9f + switchblade.MapRevealId = 26 flail.Name = "flail" flail.MaxHealth = 2400 @@ -1127,6 +1150,7 @@ object GlobalDefinitionsVehicle { flail.collision.z = CollisionZData(Array((6f, 1), (18f, 50), (30f, 150), (36f, 350), (39f, 900))) flail.maxForwardSpeed = 55f flail.mass = 73.5f + flail.MapRevealId = 24 } /** @@ -1179,6 +1203,7 @@ object GlobalDefinitionsVehicle { mosquito.collision.z = CollisionZData(Array((3f, 1), (9f, 25), (15f, 50), (18f, 75), (19.5f, 100))) mosquito.maxForwardSpeed = 120f mosquito.mass = 53.6f + mosquito.MapRevealId = 11 lightgunship.Name = "lightgunship" // Reaver lightgunship.MaxHealth = 855 // Temporary - Correct Reaver Health from pre-"Coder Madness 2" Event @@ -1219,6 +1244,7 @@ object GlobalDefinitionsVehicle { lightgunship.collision.z = CollisionZData(Array((3f, 1), (9f, 30), (15f, 60), (18f, 90), (19.5f, 125))) lightgunship.maxForwardSpeed = 104f lightgunship.mass = 51.1f + lightgunship.MapRevealId = 6 wasp.Name = "wasp" wasp.MaxHealth = 515 @@ -1257,6 +1283,7 @@ object GlobalDefinitionsVehicle { wasp.collision.z = CollisionZData(Array((3f, 1), (9f, 25), (15f, 50), (18f, 75), (19.5f, 100))) //mosquito numbers wasp.maxForwardSpeed = 120f wasp.mass = 53.6f + wasp.MapRevealId = 11 liberator.Name = "liberator" liberator.MaxHealth = 2500 @@ -1305,6 +1332,7 @@ object GlobalDefinitionsVehicle { liberator.collision.z = CollisionZData(Array((3f, 1), (9f, 30), (15f, 60), (18f, 90), (19.5f, 125))) liberator.maxForwardSpeed = 90f liberator.mass = 82f + liberator.MapRevealId = 5 vulture.Name = "vulture" vulture.MaxHealth = 2500 @@ -1354,6 +1382,7 @@ object GlobalDefinitionsVehicle { vulture.collision.z = CollisionZData(Array((3f, 1), (9f, 30), (15f, 60), (18f, 90), (19.5f, 125))) vulture.maxForwardSpeed = 97f vulture.mass = 82f + vulture.MapRevealId = 5 dropship.Name = "dropship" // Galaxy dropship.MaxHealth = 5000 @@ -1430,6 +1459,7 @@ object GlobalDefinitionsVehicle { dropship.collision.z = CollisionZData(Array((3f, 5), (9f, 125), (15f, 250), (18f, 500), (19.5f, 1000))) dropship.maxForwardSpeed = 80f dropship.mass = 133f + dropship.MapRevealId = 4 galaxy_gunship.Name = "galaxy_gunship" galaxy_gunship.MaxHealth = 6000 @@ -1490,6 +1520,7 @@ object GlobalDefinitionsVehicle { galaxy_gunship.collision.z = CollisionZData(Array((3f, 5), (9f, 125), (15f, 250), (18f, 500), (19.5f, 1000))) galaxy_gunship.maxForwardSpeed = 85f galaxy_gunship.mass = 133f + galaxy_gunship.MapRevealId = 31 lodestar.Name = "lodestar" lodestar.MaxHealth = 5000 @@ -1542,6 +1573,7 @@ object GlobalDefinitionsVehicle { lodestar.collision.z = CollisionZData(Array((3f, 5), (9f, 125), (15f, 250), (18f, 500), (19.5f, 1000))) lodestar.maxForwardSpeed = 80f lodestar.mass = 128.2f + lodestar.MapRevealId = 34 phantasm.Name = "phantasm" phantasm.MaxHealth = 2500 @@ -1587,6 +1619,7 @@ object GlobalDefinitionsVehicle { phantasm.collision.z = CollisionZData(Array((3f, 1), (9f, 30), (15f, 60), (18f, 90), (19.5f, 125))) phantasm.maxForwardSpeed = 140f phantasm.mass = 100f + phantasm.MapRevealId = 35 droppod.Name = "droppod" droppod.MaxHealth = 20000 @@ -1724,6 +1757,7 @@ object GlobalDefinitionsVehicle { aphelion_gunner.collision.z = CollisionZData(Array((25f, 2), (40f, 4), (60f, 8), (85f, 16), (115f, 32))) aphelion_gunner.maxForwardSpeed = 17 aphelion_gunner.mass = 615.1f + aphelion_gunner.MapRevealId = 44 colossus_gunner.Name = "colossus_gunner" colossus_gunner.MaxHealth = 4500 @@ -1775,6 +1809,7 @@ object GlobalDefinitionsVehicle { colossus_gunner.collision.z = CollisionZData(Array((25f, 2), (40f, 4), (60f, 8), (85f, 16), (115f, 32))) colossus_gunner.maxForwardSpeed = 17 colossus_gunner.mass = 709.7f + colossus_gunner.MapRevealId = 48 peregrine_gunner.Name = "peregrine_gunner" peregrine_gunner.MaxHealth = 4500 @@ -1826,6 +1861,7 @@ object GlobalDefinitionsVehicle { peregrine_gunner.collision.z = CollisionZData(Array((25f, 2), (40f, 4), (60f, 8), (85f, 16), (115f, 32))) peregrine_gunner.maxForwardSpeed = 17 peregrine_gunner.mass = 713f + peregrine_gunner.MapRevealId = 52 val battleFrameFlightConverter = new BattleFrameFlightConverter aphelion_flight.Name = "aphelion_flight" @@ -1881,6 +1917,7 @@ object GlobalDefinitionsVehicle { aphelion_flight.collision.z = CollisionZData(Array((25f, 2), (40f, 4), (60f, 8), (85f, 16), (115f, 32))) aphelion_flight.maxForwardSpeed = 35 aphelion_flight.mass = 615.1f + aphelion_flight.MapRevealId = 45 colossus_flight.Name = "colossus_flight" colossus_flight.MaxHealth = 3500 @@ -1935,6 +1972,7 @@ object GlobalDefinitionsVehicle { colossus_flight.collision.z = CollisionZData(Array((25f, 2), (40f, 4), (60f, 8), (85f, 16), (115f, 32))) colossus_flight.maxForwardSpeed = 34 colossus_flight.mass = 709.7f + colossus_flight.MapRevealId = 49 peregrine_flight.Name = "peregrine_flight" peregrine_flight.MaxHealth = 3500 @@ -1989,5 +2027,6 @@ object GlobalDefinitionsVehicle { peregrine_flight.collision.z = CollisionZData(Array((25f, 2), (40f, 4), (60f, 8), (85f, 16), (115f, 32))) peregrine_flight.maxForwardSpeed = 35 peregrine_flight.mass = 713f + peregrine_flight.MapRevealId = 51 } } From 3055f73a68fdcb9ccfff5b5a78160ae0c6e76e14 Mon Sep 17 00:00:00 2001 From: ScrawnyRonnie Date: Sat, 19 Jul 2025 22:10:07 -0400 Subject: [PATCH 08/19] wrong name --- .../actors/session/support/WeaponAndProjectileOperations.scala | 2 +- src/main/scala/net/psforever/objects/avatar/PlayerControl.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala index 24ba6f335..cf4c36ad4 100644 --- a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala @@ -363,7 +363,7 @@ class WeaponAndProjectileOperations( val cr = player.avatar.cr.value val strikeType = playerFaction match { case PlanetSideEmpire.NC => - if (cr == 4) {"explosion_bluedeath_nc"} else {"explosion_bluedeath_nc_lrg"} + if (cr == 4) {"explosion_bluedeath"} else {"explosion_bluedeath_lrg"} case PlanetSideEmpire.TR => if (cr == 4) {"explosion_bluedeath_tr"} else {"explosion_bluedeath_tr_lrg"} case PlanetSideEmpire.VS => diff --git a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala index 4e151f973..34d6b19a0 100644 --- a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala +++ b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala @@ -362,7 +362,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm log.info(s"${player.Name} has put ${player.Sex.possessive} ${holsteredEquipment.Definition.Name} down") //make sure the player didn't just initialte an orbital strike. If not (the if below is true), make sure waypoint is removed if (holsteredEquipment.Definition == GlobalDefinitions.command_detonater && player.avatar.cr.value > 3 && - !player.avatar.cooldowns.purchase.exists(os => os._1 == "orbital_strike" && Seconds.secondsBetween(os._2, LocalDateTime.now()).getSeconds < 10)) { + !player.avatar.cooldowns.purchase.exists(os => os._1 == "orbital_strike" && Seconds.secondsBetween(os._2, LocalDateTime.now()).getSeconds < 12)) { player.Zone.LocalEvents ! LocalServiceMessage(s"${player.Faction}", LocalAction.SendPacket(OrbitalStrikeWaypointMessage(player.GUID, None))) } case None => From 198f4cebb959ac4c882dfed09bc294743c6c6f07 Mon Sep 17 00:00:00 2001 From: ScrawnyRonnie Date: Sun, 20 Jul 2025 07:48:13 -0400 Subject: [PATCH 09/19] packet order --- .../session/support/WeaponAndProjectileOperations.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala index cf4c36ad4..5be9ab5b6 100644 --- a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala @@ -309,15 +309,15 @@ class WeaponAndProjectileOperations( avatarActor ! AvatarActor.UpdateCUDTime("reveal_friendlies") sendResponse(UplinkPositionEvent(5, Event0(5))) sendResponse(UplinkPositionEvent(4, Event1(4, revealZone))) - sendResponse(UplinkPositionEvent(6, Event0(6))) val friendlies = player.Zone.LivePlayers.filter { friend => friend.Faction == player.Faction } val friendlyVehicles = player.Zone.Vehicles.filter { vehicle => vehicle.Faction == player.Faction && !vehicle.Destroyed } friendlies.foreach { f => - sendResponse(UplinkPositionEvent(0, Event2(0, Vector3(f.Position.x, f.Position.y, 0.0f), 255, revealZone, 0, 1118938442, 300000, 299080, Some(true)))) + sendResponse(UplinkPositionEvent(0, Event2(0, Vector3(f.Position.x, f.Position.y, 0.0f), 255, revealZone, 0, 1127348721, 300000, 298858, Some(true)))) } friendlyVehicles.foreach { v => - sendResponse(UplinkPositionEvent(0, Event2(0, Vector3(v.Position.x, v.Position.y, 0.0f), v.Definition.MapRevealId, revealZone, 0, 1118938442, 300000, 299080, Some(true)))) + sendResponse(UplinkPositionEvent(0, Event2(0, Vector3(v.Position.x, v.Position.y, 0.0f), v.Definition.MapRevealId, revealZone, 0, 1127348721, 300000, 298858, Some(true)))) } + sendResponse(UplinkPositionEvent(6, Event0(6))) case UplinkRequestType.RevealEnemies => val revealZone = player.Zone.Number sendResponse(UplinkResponse(code.value, 0)) @@ -325,7 +325,6 @@ class WeaponAndProjectileOperations( avatarActor ! AvatarActor.UpdateCUDTime("reveal_enemies") sendResponse(UplinkPositionEvent(5, Event0(5))) sendResponse(UplinkPositionEvent(4, Event1(4, revealZone))) - sendResponse(UplinkPositionEvent(6, Event0(6))) val enemies = player.Zone.LivePlayers.filter { enemy => enemy.Faction != player.Faction && Zone.orbitalStrikeDistanceCheck(player.Position, enemy.Position, 200f)} //reusing distance check val enemyVehicles = player.Zone.Vehicles.filter { vehicle => vehicle.Faction != player.Faction && !vehicle.Destroyed && @@ -336,6 +335,7 @@ class WeaponAndProjectileOperations( enemyVehicles.foreach { v => sendResponse(UplinkPositionEvent(1, Event2(1, Vector3(v.Position.x, v.Position.y, 0.0f), v.Definition.MapRevealId, revealZone, 0, 1118938442, 300000, 299080, Some(false)))) } + sendResponse(UplinkPositionEvent(6, Event0(6))) case UplinkRequestType.ElectroMagneticPulse => val cr = player.avatar.cr.value val empSize = cr match { From 697d5cfba9039758d5a019f0733992ea5a0385d8 Mon Sep 17 00:00:00 2001 From: ScrawnyRonnie Date: Mon, 21 Jul 2025 07:51:23 -0400 Subject: [PATCH 10/19] found a friendly --- .../support/WeaponAndProjectileOperations.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala index 5be9ab5b6..8e18811e3 100644 --- a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala @@ -308,14 +308,14 @@ class WeaponAndProjectileOperations( sendResponse(PlanetsideAttributeMessage(player.GUID, 57, 1200000)) avatarActor ! AvatarActor.UpdateCUDTime("reveal_friendlies") sendResponse(UplinkPositionEvent(5, Event0(5))) - sendResponse(UplinkPositionEvent(4, Event1(4, revealZone))) + sendResponse(UplinkPositionEvent(3, Event1(3, revealZone))) val friendlies = player.Zone.LivePlayers.filter { friend => friend.Faction == player.Faction } val friendlyVehicles = player.Zone.Vehicles.filter { vehicle => vehicle.Faction == player.Faction && !vehicle.Destroyed } friendlies.foreach { f => - sendResponse(UplinkPositionEvent(0, Event2(0, Vector3(f.Position.x, f.Position.y, 0.0f), 255, revealZone, 0, 1127348721, 300000, 298858, Some(true)))) + sendResponse(UplinkPositionEvent(0, Event2(0, Vector3(f.Position.x, f.Position.y, 0.0f), 255, revealZone, 0, 1117348721, 300000, 299497, Some(true)))) } friendlyVehicles.foreach { v => - sendResponse(UplinkPositionEvent(0, Event2(0, Vector3(v.Position.x, v.Position.y, 0.0f), v.Definition.MapRevealId, revealZone, 0, 1127348721, 300000, 298858, Some(true)))) + sendResponse(UplinkPositionEvent(0, Event2(0, Vector3(v.Position.x, v.Position.y, 0.0f), v.Definition.MapRevealId, revealZone, 0, 1127348721, 300000, 299497, Some(true)))) } sendResponse(UplinkPositionEvent(6, Event0(6))) case UplinkRequestType.RevealEnemies => @@ -330,10 +330,10 @@ class WeaponAndProjectileOperations( val enemyVehicles = player.Zone.Vehicles.filter { vehicle => vehicle.Faction != player.Faction && !vehicle.Destroyed && Zone.orbitalStrikeDistanceCheck(player.Position, vehicle.Position, 200f)} //reusing distance check enemies.foreach { e => - sendResponse(UplinkPositionEvent(1, Event2(1, Vector3(e.Position.x, e.Position.y, 0.0f), 255, revealZone, 0, 1118938442, 300000, 299080, Some(false)))) + sendResponse(UplinkPositionEvent(1, Event2(1, Vector3(e.Position.x, e.Position.y, 0.0f), 255, revealZone, 0, 1138938442, 300000, 299080, Some(false)))) } enemyVehicles.foreach { v => - sendResponse(UplinkPositionEvent(1, Event2(1, Vector3(v.Position.x, v.Position.y, 0.0f), v.Definition.MapRevealId, revealZone, 0, 1118938442, 300000, 299080, Some(false)))) + sendResponse(UplinkPositionEvent(1, Event2(1, Vector3(v.Position.x, v.Position.y, 0.0f), v.Definition.MapRevealId, revealZone, 0, 1148938442, 300000, 299080, Some(false)))) } sendResponse(UplinkPositionEvent(6, Event0(6))) case UplinkRequestType.ElectroMagneticPulse => From 4f50884c7102abcddb0c5cac97457d794eac2885 Mon Sep 17 00:00:00 2001 From: ScrawnyRonnie Date: Mon, 21 Jul 2025 10:38:08 -0400 Subject: [PATCH 11/19] 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 cbb9bc88e..6d56004c6 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 a60ac1edc..503e7757a 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 8cfd403d8..334921248 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 cf2634b26..adcf381b1 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 230aa2226..bb319dccd 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 0510d5c07..4c5a4171a 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 13d7d5c90..ef1314248 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 +) From 01cdc2db4cf1ed32ea9a1f901d3d76ea13652f48 Mon Sep 17 00:00:00 2001 From: ScrawnyRonnie Date: Tue, 22 Jul 2025 12:28:54 -0400 Subject: [PATCH 12/19] adjusted condition and weather --- src/main/resources/application.conf | 6 ++-- .../session/support/ZoningOperations.scala | 28 ++++++++++++++++++- .../MajorFacilityHackParticipation.scala | 16 +++++++++-- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index 6d56004c6..27b1e4a5b 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -514,11 +514,11 @@ game { # 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 + yellow = 20 # Wiki says 30-60 - orange = 2 + orange = 30 # Wiki says 60+ - red = 3 + red = 60 } } 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 503e7757a..35ccd88ba 100644 --- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala @@ -18,7 +18,7 @@ import net.psforever.objects.serverobject.turret.auto.AutomatedTurret import net.psforever.objects.sourcing.{PlayerSource, SourceEntry, VehicleSource} import net.psforever.objects.vital.{InGameHistory, IncarnationActivity, ReconstructionActivity, SpawningActivity} import net.psforever.objects.zones.blockmap.BlockMapEntity -import net.psforever.packet.game.{CampaignStatistic, ChangeFireStateMessage_Start, HackState7, MailMessage, ObjectDetectedMessage, SessionStatistic, TriggeredSound} +import net.psforever.packet.game.{CampaignStatistic, ChangeFireStateMessage_Start, HackState7, MailMessage, ObjectDetectedMessage, SessionStatistic, TriggeredSound, WeatherMessage, CloudInfo, StormInfo} import net.psforever.services.chat.DefaultChannel import scala.collection.mutable @@ -2504,6 +2504,19 @@ class ZoningOperations( sessionLogic.general.toggleTeleportSystem(obj, TelepadLike.AppraiseTeleportationSystem(obj, continent)) } } + //make weather happen + sendResponse(WeatherMessage(List(),List( + StormInfo(Vector3(0.1f, 0.15f, 0.0f), 240, 217), + StormInfo(Vector3(0.5f, 0.11f, 0.0f), 240, 215), + StormInfo(Vector3(0.15f, 0.4f, 0.0f), 249, 215), + StormInfo(Vector3(0.15f, 0.87f, 0.0f), 240, 215), + StormInfo(Vector3(0.3f, 0.65f, 0.0f), 240, 215), + StormInfo(Vector3(0.5f, 0.475f, 0.0f), 245, 215), + StormInfo(Vector3(0.725f, 0.38f, 0.0f), 243, 215), + StormInfo(Vector3(0.9f, 0.57f, 0.0f), 244, 215), + StormInfo(Vector3(0.9f, 0.9f, 0.0f), 243, 215), + StormInfo(Vector3(0.1f, 0.2f, 0.0f), 241, 215), + StormInfo(Vector3(0.95f, 0.2f, 0.0f), 241, 215)))) //begin looking for conditions to set the avatar context.system.scheduler.scheduleOnce(delay = 250 millisecond, context.self, SessionActor.SetCurrentAvatar(player, 200)) } @@ -2615,6 +2628,19 @@ class ZoningOperations( } avatarActor ! AvatarActor.RefreshPurchaseTimes() setupAvatarFunc = AvatarCreate + //make weather happen + sendResponse(WeatherMessage(List(),List( + StormInfo(Vector3(0.1f, 0.15f, 0.0f), 240, 217), + StormInfo(Vector3(0.5f, 0.11f, 0.0f), 240, 215), + StormInfo(Vector3(0.15f, 0.4f, 0.0f), 249, 215), + StormInfo(Vector3(0.15f, 0.87f, 0.0f), 240, 215), + StormInfo(Vector3(0.3f, 0.65f, 0.0f), 240, 215), + StormInfo(Vector3(0.5f, 0.475f, 0.0f), 245, 215), + StormInfo(Vector3(0.725f, 0.38f, 0.0f), 243, 215), + StormInfo(Vector3(0.9f, 0.57f, 0.0f), 244, 215), + StormInfo(Vector3(0.9f, 0.9f, 0.0f), 243, 215), + StormInfo(Vector3(0.1f, 0.2f, 0.0f), 241, 215), + StormInfo(Vector3(0.95f, 0.2f, 0.0f), 241, 215)))) //begin looking for conditions to set the avatar context.system.scheduler.scheduleOnce(delay = 750 millisecond, context.self, SessionActor.SetCurrentAvatar(player, 200)) } 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 bb319dccd..fdd519ec5 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 @@ -28,6 +28,7 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci private var hotSpotLayersOverTime: Seq[List[HotSpotInfo]] = Seq[List[HotSpotInfo]]() var lastEnemyCount: List[Player] = List.empty + var alertTimeMillis: Long = 0L def TryUpdate(): Unit = { val list = building.PlayersInSOI @@ -44,10 +45,21 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci 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) { + //straight away if higher alert, delay if pop decreases enough to lower alert + if ((enemies.length >= Config.app.game.alert.yellow && lastEnemyCount.length < Config.app.game.alert.yellow) || + (enemies.length >= Config.app.game.alert.orange && lastEnemyCount.length < Config.app.game.alert.orange) || + (enemies.length >= Config.app.game.alert.red && lastEnemyCount.length < Config.app.game.alert.red) || + (enemies.length < Config.app.game.alert.yellow && lastEnemyCount.length >= Config.app.game.alert.yellow && + now - alertTimeMillis > 30000L && Math.abs(enemies.length - lastEnemyCount.length) >= 3) || + (enemies.length < Config.app.game.alert.orange && lastEnemyCount.length >= Config.app.game.alert.orange && + now - alertTimeMillis > 30000L && Math.abs(enemies.length - lastEnemyCount.length) >= 3) || + (enemies.length < Config.app.game.alert.red && lastEnemyCount.length >= Config.app.game.alert.red && + now - alertTimeMillis > 30000L && Math.abs(enemies.length - lastEnemyCount.length) >= 3)) + { building.Actor ! BuildingActor.DensityLevelUpdate(building) + alertTimeMillis = now + lastEnemyCount = enemies } - lastEnemyCount = enemies building.CaptureTerminal .map(_.HackedBy) .collect { From 39e70ff3193cc8b92151eed04705d79b45f26169 Mon Sep 17 00:00:00 2001 From: ScrawnyRonnie Date: Thu, 24 Jul 2025 13:18:01 -0400 Subject: [PATCH 13/19] time change why not --- .../net/psforever/actors/session/support/ZoningOperations.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1932cdb63..fd7e3145c 100644 --- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala @@ -292,7 +292,7 @@ class ZoningOperations( continent.VehicleEvents ! Service.Join(continentId) continent.VehicleEvents ! Service.Join(factionChannel) if (sessionLogic.connectionState != 100) configZone(continent) - sendResponse(TimeOfDayMessage(1191182336)) + sendResponse(TimeOfDayMessage(1135214592)) //custom sendResponse(ReplicationStreamMessage(5, Some(6), Vector.empty)) //clear squad list sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 0)) // disable festive backpacks From 2b1d01c4bafb700658617720368dfe7e3b6467e2 Mon Sep 17 00:00:00 2001 From: ScrawnyRonnie Date: Mon, 28 Jul 2025 21:26:39 -0400 Subject: [PATCH 14/19] misc animations and turret range --- .../actors/session/csr/GeneralLogic.scala | 5 +++++ .../actors/session/normal/GeneralLogic.scala | 5 +++++ .../session/normal/VehicleHandlerLogic.scala | 2 +- .../spectator/VehicleHandlerLogic.scala | 2 +- .../session/support/ZoningOperations.scala | 12 ++++++++--- .../damage/DamageableEntity.scala | 12 +++++++---- .../turret/auto/AutomatedTurretBehavior.scala | 21 +++++++++++++------ .../game/GenericObjectActionMessage.scala | 1 + .../avatar/support/CorpseRemovalActor.scala | 2 +- 9 files changed, 46 insertions(+), 16 deletions(-) diff --git a/src/main/scala/net/psforever/actors/session/csr/GeneralLogic.scala b/src/main/scala/net/psforever/actors/session/csr/GeneralLogic.scala index 68f59b434..d9c76b6ef 100644 --- a/src/main/scala/net/psforever/actors/session/csr/GeneralLogic.scala +++ b/src/main/scala/net/psforever/actors/session/csr/GeneralLogic.scala @@ -33,6 +33,7 @@ import net.psforever.packet.PlanetSideGamePacket import net.psforever.packet.game.{ActionCancelMessage, AvatarFirstTimeEventMessage, AvatarImplantMessage, AvatarJumpMessage, BattleplanMessage, BindPlayerMessage, BugReportMessage, ChangeFireModeMessage, ChangeShortcutBankMessage, CharacterCreateRequestMessage, CharacterRequestMessage, ChatMsg, CollisionIs, ConnectToWorldRequestMessage, CreateShortcutMessage, DeadState, DeployObjectMessage, DisplayedAwardMessage, DropItemMessage, EmoteMsg, FacilityBenefitShieldChargeRequestMessage, FriendsRequest, GenericAction, GenericActionMessage, GenericCollisionMsg, GenericObjectActionAtPositionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HitHint, InvalidTerrainMessage, LootItemMessage, MoveItemMessage, ObjectDetectedMessage, ObjectHeldMessage, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, RequestDestroyMessage, TargetingImplantRequest, TerrainCondition, TradeMessage, UnuseItemMessage, UseItemMessage, VoiceHostInfo, VoiceHostRequest, ZipLineMessage} import net.psforever.services.RemoverActor import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} +import net.psforever.services.local.{LocalAction, LocalServiceMessage} import net.psforever.types.{CapacitorStateType, ChatMessageType, Cosmetic, ExoSuitType, PlanetSideEmpire, PlanetSideGUID, Vector3} import scala.util.Success @@ -181,7 +182,11 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex def handleEmote(pkt: EmoteMsg): Unit = { val EmoteMsg(avatarGuid, emote) = pkt + val pZone = player.Zone sendResponse(EmoteMsg(avatarGuid, emote)) + pZone.blockMap.sector(player).livePlayerList.collect { case t if t.GUID != player.GUID => + pZone.LocalEvents ! LocalServiceMessage(t.Name, LocalAction.SendResponse(EmoteMsg(avatarGuid, emote))) + } } def handleDropItem(pkt: DropItemMessage): Unit = { diff --git a/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala b/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala index fdfa1c9b4..23e9d38ed 100644 --- a/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala @@ -40,6 +40,7 @@ import net.psforever.packet.PlanetSideGamePacket import net.psforever.packet.game.{ActionCancelMessage, ActionResultMessage, AvatarFirstTimeEventMessage, AvatarImplantMessage, AvatarJumpMessage, BattleplanMessage, BindPlayerMessage, BugReportMessage, ChangeFireModeMessage, ChangeShortcutBankMessage, CharacterCreateRequestMessage, CharacterRequestAction, CharacterRequestMessage, ChatMsg, CollisionIs, ConnectToWorldRequestMessage, CreateShortcutMessage, DeadState, DeployObjectMessage, DisplayedAwardMessage, DropItemMessage, EmoteMsg, FacilityBenefitShieldChargeRequestMessage, FriendsRequest, GenericAction, GenericActionMessage, GenericCollisionMsg, GenericObjectActionAtPositionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HitHint, InvalidTerrainMessage, LootItemMessage, MoveItemMessage, ObjectDetectedMessage, ObjectHeldMessage, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, RequestDestroyMessage, TargetingImplantRequest, TerrainCondition, TradeMessage, UnuseItemMessage, UseItemMessage, VoiceHostInfo, VoiceHostRequest, ZipLineMessage} import net.psforever.services.account.{AccountPersistenceService, RetrieveAccountData} import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} +import net.psforever.services.local.{LocalAction, LocalServiceMessage} import net.psforever.services.local.support.CaptureFlagManager import net.psforever.types.{CapacitorStateType, ChatMessageType, Cosmetic, ExoSuitType, ImplantType, PlanetSideEmpire, PlanetSideGUID, Vector3} import net.psforever.util.Config @@ -199,7 +200,11 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex def handleEmote(pkt: EmoteMsg): Unit = { val EmoteMsg(avatarGuid, emote) = pkt + val pZone = player.Zone sendResponse(EmoteMsg(avatarGuid, emote)) + pZone.blockMap.sector(player).livePlayerList.collect { case t if t.GUID != player.GUID => + pZone.LocalEvents ! LocalServiceMessage(t.Name, LocalAction.SendResponse(EmoteMsg(avatarGuid, emote))) + } } def handleDropItem(pkt: DropItemMessage): Unit = { diff --git a/src/main/scala/net/psforever/actors/session/normal/VehicleHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/normal/VehicleHandlerLogic.scala index 64a7a67a8..f6102dedb 100644 --- a/src/main/scala/net/psforever/actors/session/normal/VehicleHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/VehicleHandlerLogic.scala @@ -231,7 +231,7 @@ class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context: sendResponse(ObjectCreateDetailedMessage(itemType, itemGuid, ObjectCreateMessageParent(vehicleGuid, slot), itemData)) case VehicleResponse.UnloadVehicle(_, vehicleGuid) => - sendResponse(ObjectDeleteMessage(vehicleGuid, unk1=0)) + sendResponse(ObjectDeleteMessage(vehicleGuid, unk1=1)) if (sessionLogic.zoning.spawn.prevSpawnPoint.map(_.Owner).exists { case ams: Vehicle => ams.GUID == vehicleGuid && diff --git a/src/main/scala/net/psforever/actors/session/spectator/VehicleHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/spectator/VehicleHandlerLogic.scala index ac930161c..d68308a68 100644 --- a/src/main/scala/net/psforever/actors/session/spectator/VehicleHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/spectator/VehicleHandlerLogic.scala @@ -197,7 +197,7 @@ class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context: sendResponse(PlanetsideAttributeMessage(vehicleGuid, seatGroup, permission)) case VehicleResponse.UnloadVehicle(_, vehicleGuid) => - sendResponse(ObjectDeleteMessage(vehicleGuid, unk1=0)) + sendResponse(ObjectDeleteMessage(vehicleGuid, unk1=1)) case VehicleResponse.UnstowEquipment(itemGuid) if isNotSameTarget => //TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly? 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 fd7e3145c..f46ba3185 100644 --- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala @@ -18,7 +18,8 @@ import net.psforever.objects.serverobject.turret.auto.AutomatedTurret import net.psforever.objects.sourcing.{PlayerSource, SourceEntry, VehicleSource} import net.psforever.objects.vital.{InGameHistory, IncarnationActivity, ReconstructionActivity, SpawningActivity} import net.psforever.objects.zones.blockmap.BlockMapEntity -import net.psforever.packet.game.{CampaignStatistic, ChangeFireStateMessage_Start, HackState7, MailMessage, ObjectDetectedMessage, SessionStatistic, TriggeredSound, WeatherMessage, CloudInfo, StormInfo} +import net.psforever.packet.game.GenericAction.FirstPersonViewWithEffect +import net.psforever.packet.game.{CampaignStatistic, ChangeFireStateMessage_Start, CloudInfo, GenericActionMessage, GenericObjectActionEnum, HackState7, MailMessage, ObjectDetectedMessage, SessionStatistic, StormInfo, TriggeredSound, WeatherMessage} import net.psforever.services.chat.DefaultChannel import scala.collection.mutable @@ -2972,10 +2973,10 @@ class ZoningOperations( case _ if player.HasGUID => // player is deconstructing self or instant action val player_guid = player.GUID - sendResponse(ObjectDeleteMessage(player_guid, 4)) + sendResponse(ObjectDeleteMessage(player_guid, unk1=1)) continent.AvatarEvents ! AvatarServiceMessage( continent.id, - AvatarAction.ObjectDelete(player_guid, player_guid, 4) + AvatarAction.ObjectDelete(player_guid, player_guid, unk=1) ) InGameHistory.SpawnReconstructionActivity(player, toZoneNumber, betterSpawnPoint) LoadZoneAsPlayerUsing(player, pos, ori, toSide, zoneId) @@ -3776,6 +3777,11 @@ class ZoningOperations( player.death_by = 1 } GoToDeploymentMap() + val pZone = player.Zone + sendResponse(GenericActionMessage(FirstPersonViewWithEffect)) + pZone.blockMap.sector(player).livePlayerList.collect { case t if t.GUID != player.GUID => + pZone.LocalEvents ! LocalServiceMessage(t.Name, LocalAction.SendGenericObjectActionMessage(t.GUID, player.GUID, GenericObjectActionEnum.PlayerDeconstructs)) + } } def stopDeconstructing(): Unit = { diff --git a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableEntity.scala b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableEntity.scala index b3d74ac17..e594de2db 100644 --- a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableEntity.scala +++ b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableEntity.scala @@ -2,6 +2,7 @@ package net.psforever.objects.serverobject.damage import net.psforever.objects.equipment.JammableUnit +import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects.vital.interaction.DamageResult import net.psforever.objects.vital.resolution.ResolutionCalculations import net.psforever.objects.zones.Zone @@ -199,9 +200,12 @@ object DamageableEntity { val tguid = target.GUID val attribution = attributionTo(cause, target.Zone) zone.AvatarEvents ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 0, target.Health)) - zone.AvatarEvents ! AvatarServiceMessage( - zoneId, - AvatarAction.Destroy(tguid, attribution, Service.defaultPlayerGUID, target.Position) - ) + if (target.isInstanceOf[SpawnTube]) {}//do nothing to prevent issue #1057 + else { + zone.AvatarEvents ! AvatarServiceMessage( + zoneId, + AvatarAction.Destroy(tguid, attribution, Service.defaultPlayerGUID, target.Position) + ) + } } } diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/auto/AutomatedTurretBehavior.scala b/src/main/scala/net/psforever/objects/serverobject/turret/auto/AutomatedTurretBehavior.scala index fad4c5c34..cd116dfd5 100644 --- a/src/main/scala/net/psforever/objects/serverobject/turret/auto/AutomatedTurretBehavior.scala +++ b/src/main/scala/net/psforever/objects/serverobject/turret/auto/AutomatedTurretBehavior.scala @@ -205,7 +205,8 @@ trait AutomatedTurretBehavior { val now = System.currentTimeMillis() if ( currentTargetToken.isEmpty && - target.Faction != AutomatedTurretObject.Faction + target.Faction != AutomatedTurretObject.Faction && + now >= currentTargetLastShotTime ) { currentTargetLastShotTime = now currentTargetLocation = Some(target.Position) @@ -215,11 +216,19 @@ trait AutomatedTurretBehavior { true } else if ( currentTargetToken.contains(SourceUniqueness(target)) && - now - currentTargetLastShotTime < autoStats.map(_.cooldowns.missedShot).getOrElse(0L)) { - currentTargetLastShotTime = now - currentTargetLocation = Some(target.Position) - cancelSelfReportedAutoFire() - true + now - currentTargetLastShotTime < autoStats.map(_.cooldowns.missedShot).getOrElse(0L) + ) { + val escapeRange = autoStats.map(_.ranges.escape).getOrElse(400f) + val distSq = Vector3.DistanceSquared(target.Position, AutomatedTurretObject.Position) + val escapeSq = escapeRange * escapeRange + if (distSq <= escapeSq) { + currentTargetLastShotTime = now + currentTargetLocation = Some(target.Position) + cancelSelfReportedAutoFire() + true + } else { + false + } } else { false } diff --git a/src/main/scala/net/psforever/packet/game/GenericObjectActionMessage.scala b/src/main/scala/net/psforever/packet/game/GenericObjectActionMessage.scala index 916ee0310..aa71a534c 100644 --- a/src/main/scala/net/psforever/packet/game/GenericObjectActionMessage.scala +++ b/src/main/scala/net/psforever/packet/game/GenericObjectActionMessage.scala @@ -91,4 +91,5 @@ object GenericObjectActionEnum extends Enumeration { * Target: CaptureTerminal */ val FlagSpawned = Value(14) + val PlayerDeconstructs = Value(6) } diff --git a/src/main/scala/net/psforever/services/avatar/support/CorpseRemovalActor.scala b/src/main/scala/net/psforever/services/avatar/support/CorpseRemovalActor.scala index 062c7f15c..974f0b687 100644 --- a/src/main/scala/net/psforever/services/avatar/support/CorpseRemovalActor.scala +++ b/src/main/scala/net/psforever/services/avatar/support/CorpseRemovalActor.scala @@ -25,7 +25,7 @@ class CorpseRemovalActor extends RemoverActor() { entry.zone.Population ! Zone.Corpse.Remove(entry.obj.asInstanceOf[Player]) context.parent ! AvatarServiceMessage( entry.zone.id, - AvatarAction.ObjectDelete(Service.defaultPlayerGUID, entry.obj.GUID) + AvatarAction.ObjectDelete(Service.defaultPlayerGUID, entry.obj.GUID, unk=1) ) } From aa31d6e3954074a10e0ecaf7fd704c746a96c868 Mon Sep 17 00:00:00 2001 From: ScrawnyRonnie Date: Wed, 30 Jul 2025 10:39:50 -0400 Subject: [PATCH 15/19] gm sees anims and crouch bfr --- .../actors/session/csr/GeneralLogic.scala | 3 +++ .../actors/session/normal/GeneralLogic.scala | 3 +++ .../session/support/ZoningOperations.scala | 3 +++ .../scala/net/psforever/objects/Vehicles.scala | 16 +++++++++++++++- .../packet/game/GenericObjectActionMessage.scala | 1 + 5 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/main/scala/net/psforever/actors/session/csr/GeneralLogic.scala b/src/main/scala/net/psforever/actors/session/csr/GeneralLogic.scala index d9c76b6ef..2cd1782ec 100644 --- a/src/main/scala/net/psforever/actors/session/csr/GeneralLogic.scala +++ b/src/main/scala/net/psforever/actors/session/csr/GeneralLogic.scala @@ -187,6 +187,9 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex pZone.blockMap.sector(player).livePlayerList.collect { case t if t.GUID != player.GUID => pZone.LocalEvents ! LocalServiceMessage(t.Name, LocalAction.SendResponse(EmoteMsg(avatarGuid, emote))) } + pZone.AllPlayers.collect { case t if t.GUID != player.GUID && !t.allowInteraction => + pZone.LocalEvents ! LocalServiceMessage(t.Name, LocalAction.SendResponse(EmoteMsg(avatarGuid, emote))) + } } def handleDropItem(pkt: DropItemMessage): Unit = { diff --git a/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala b/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala index 23e9d38ed..99cf5103c 100644 --- a/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala @@ -205,6 +205,9 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex pZone.blockMap.sector(player).livePlayerList.collect { case t if t.GUID != player.GUID => pZone.LocalEvents ! LocalServiceMessage(t.Name, LocalAction.SendResponse(EmoteMsg(avatarGuid, emote))) } + pZone.AllPlayers.collect { case t if t.GUID != player.GUID && !t.allowInteraction => + pZone.LocalEvents ! LocalServiceMessage(t.Name, LocalAction.SendResponse(EmoteMsg(avatarGuid, emote))) + } } def handleDropItem(pkt: DropItemMessage): Unit = { 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 f46ba3185..c2404d291 100644 --- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala @@ -3782,6 +3782,9 @@ class ZoningOperations( pZone.blockMap.sector(player).livePlayerList.collect { case t if t.GUID != player.GUID => pZone.LocalEvents ! LocalServiceMessage(t.Name, LocalAction.SendGenericObjectActionMessage(t.GUID, player.GUID, GenericObjectActionEnum.PlayerDeconstructs)) } + pZone.AllPlayers.collect { case t if t.GUID != player.GUID && !t.allowInteraction => + pZone.LocalEvents ! LocalServiceMessage(t.Name, LocalAction.SendGenericObjectActionMessage(t.GUID, player.GUID, GenericObjectActionEnum.PlayerDeconstructs)) + } } def stopDeconstructing(): Unit = { diff --git a/src/main/scala/net/psforever/objects/Vehicles.scala b/src/main/scala/net/psforever/objects/Vehicles.scala index 259dc2845..019f4d7f2 100644 --- a/src/main/scala/net/psforever/objects/Vehicles.scala +++ b/src/main/scala/net/psforever/objects/Vehicles.scala @@ -10,7 +10,7 @@ import net.psforever.objects.serverobject.transfer.TransferContainer import net.psforever.objects.serverobject.structures.WarpGate import net.psforever.objects.vehicles._ import net.psforever.objects.zones.Zone -import net.psforever.packet.game.{ChatMsg, HackMessage, HackState, HackState1, HackState7, TriggeredSound} +import net.psforever.packet.game.{ChatMsg, FrameVehicleStateMessage, GenericObjectActionEnum, GenericObjectActionMessage, HackMessage, HackState, HackState1, HackState7, TriggeredSound, VehicleStateMessage} import net.psforever.types.{ChatMessageType, DriveState, PlanetSideEmpire, PlanetSideGUID, Vector3} import net.psforever.services.Service import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} @@ -274,6 +274,20 @@ object Vehicles { VehicleAction.KickPassenger(player.GUID, 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 ! LocalServiceMessage( + zoneid, + LocalAction.SendGenericObjectActionMessage(PlanetSideGUID(-1), target.GUID, GenericObjectActionEnum.BFRShieldsDown)) + zone.LocalEvents ! LocalServiceMessage( + zoneid, + LocalAction.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 ! LocalServiceMessage( + zoneid, + LocalAction.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 if (target.Definition.CanFly && target.isFlying) { diff --git a/src/main/scala/net/psforever/packet/game/GenericObjectActionMessage.scala b/src/main/scala/net/psforever/packet/game/GenericObjectActionMessage.scala index aa71a534c..8d4b7e4cb 100644 --- a/src/main/scala/net/psforever/packet/game/GenericObjectActionMessage.scala +++ b/src/main/scala/net/psforever/packet/game/GenericObjectActionMessage.scala @@ -92,4 +92,5 @@ object GenericObjectActionEnum extends Enumeration { */ val FlagSpawned = Value(14) val PlayerDeconstructs = Value(6) + val BFRShieldsDown = Value(45) } From a00a95d15234ea5860674cc189f08473224f461c Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Thu, 31 Jul 2025 01:12:16 -0400 Subject: [PATCH 16/19] allowing scaled back bfr accessibility (#1280) --- .../resources/overrides/game_objects0.adb.lst | 30 +++++++++++++---- .../WeaponAndProjectileOperations.scala | 2 +- .../BattleFrameFlightConverter.scala | 2 +- .../BattleFrameRoboticsConverter.scala | 2 +- .../global/GlobalDefinitionsProjectile.scala | 4 +-- .../global/GlobalDefinitionsVehicle.scala | 32 +++++++++---------- .../objects/vehicles/VehicleSubsystem.scala | 2 +- .../objects/vehicles/control/BfrControl.scala | 15 +++++---- .../vehicles/control/BfrFlightControl.scala | 8 ++--- .../resolution/ResolutionCalculations.scala | 2 +- 10 files changed, 59 insertions(+), 40 deletions(-) diff --git a/server/src/main/resources/overrides/game_objects0.adb.lst b/server/src/main/resources/overrides/game_objects0.adb.lst index 1ea38dca0..b62b955a0 100644 --- a/server/src/main/resources/overrides/game_objects0.adb.lst +++ b/server/src/main/resources/overrides/game_objects0.adb.lst @@ -96,12 +96,30 @@ add_property suppressor holstertime 600 add_property trek equiptime 500 add_property trek holstertime 500 add_property vulture requirement_award0 false -add_property aphelion allowed false +add_property aphelion allowed true add_property aphelion_flight allowed false -add_property aphelion_gunner allowed false -add_property colossus allowed false +add_property aphelion_gunner allowed true +add_property aphelion_armor_siphon allowed false +add_property aphelion_armor_siphon_left allowed false +add_property aphelion_armor_siphon_right allowed false +add_property aphelion_ntu_siphon allowed false +add_property aphelion_ntu_siphon_left allowed false +add_property aphelion_ntu_siphon_right allowed false +add_property colossus allowed true add_property colossus_flight allowed false -add_property colossus_gunner allowed false -add_property peregrine allowed false +add_property colossus_gunner allowed true +add_property colossus_armor_siphon allowed false +add_property colossus_armor_siphon_left allowed false +add_property colossus_armor_siphon_right allowed false +add_property colossus_ntu_siphon allowed false +add_property colossus_ntu_siphon_left allowed false +add_property colossus_ntu_siphon_right allowed false +add_property peregrine allowed true add_property peregrine_flight allowed false -add_property peregrine_gunner allowed false +add_property peregrine_gunner allowed true +add_property peregrine_armor_siphon allowed false +add_property peregrine_armor_siphon_left allowed false +add_property peregrine_armor_siphon_right allowed false +add_property peregrine_ntu_siphon allowed false +add_property peregrine_ntu_siphon_left allowed false +add_property peregrine_ntu_siphon_right allowed false diff --git a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala index 8e18811e3..047983bd9 100644 --- a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala @@ -870,7 +870,7 @@ class WeaponAndProjectileOperations( "BattleframeLeftArm" } else { "BattleframeRightArm" - }).get.Enabled + }).exists(_.Enabled) if (!mountIsEnabled) { //can't stop the local discharge, but it will not actually shoot anything; assert the magazine sendResponse(QuantityUpdateMessage(tool.AmmoSlot.Box.GUID, tool.Magazine)) diff --git a/src/main/scala/net/psforever/objects/definition/converter/BattleFrameFlightConverter.scala b/src/main/scala/net/psforever/objects/definition/converter/BattleFrameFlightConverter.scala index 7ed207187..9efc677c2 100644 --- a/src/main/scala/net/psforever/objects/definition/converter/BattleFrameFlightConverter.scala +++ b/src/main/scala/net/psforever/objects/definition/converter/BattleFrameFlightConverter.scala @@ -110,6 +110,6 @@ class BattleFrameFlightConverter extends ObjectCreateConverter[Vehicle]() { } def showBfrShield(obj: Vehicle): Boolean = { - obj.Subsystems(VehicleSubsystemEntry.BattleframeShieldGenerator).get.Enabled && obj.Shields > 0 + obj.Subsystems(VehicleSubsystemEntry.BattleframeShieldGenerator).exists(_.Enabled) && obj.Shields > 0 } } diff --git a/src/main/scala/net/psforever/objects/definition/converter/BattleFrameRoboticsConverter.scala b/src/main/scala/net/psforever/objects/definition/converter/BattleFrameRoboticsConverter.scala index 411ff6a79..ea12b8120 100644 --- a/src/main/scala/net/psforever/objects/definition/converter/BattleFrameRoboticsConverter.scala +++ b/src/main/scala/net/psforever/objects/definition/converter/BattleFrameRoboticsConverter.scala @@ -110,6 +110,6 @@ class BattleFrameRoboticsConverter extends ObjectCreateConverter[Vehicle]() { } def showBfrShield(obj: Vehicle): Boolean = { - obj.Subsystems(VehicleSubsystemEntry.BattleframeShieldGenerator).get.Enabled && obj.Shields > 0 + obj.Subsystems(VehicleSubsystemEntry.BattleframeShieldGenerator).exists(_.Enabled) && obj.Shields > 0 } } diff --git a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsProjectile.scala b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsProjectile.scala index 4771f421b..be9f4e581 100644 --- a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsProjectile.scala +++ b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsProjectile.scala @@ -2056,7 +2056,7 @@ object GlobalDefinitionsProjectile { aphelion_plasma_rocket_projectile.DamageAtEdge = .1f aphelion_plasma_rocket_projectile.DamageRadius = 3f aphelion_plasma_rocket_projectile.ProjectileDamageType = DamageType.Splash - aphelion_plasma_rocket_projectile.DamageProxy = 96 //aphelion_plama_cloud + //aphelion_plasma_rocket_projectile.DamageProxy = 96 //aphelion_plama_cloud aphelion_plasma_rocket_projectile.InitialVelocity = 75 aphelion_plasma_rocket_projectile.Lifespan = 5f ProjectileDefinition.CalculateDerivedFields(aphelion_plasma_rocket_projectile) @@ -2221,7 +2221,7 @@ object GlobalDefinitionsProjectile { peregrine_particle_cannon_projectile.DamageAtEdge = 0.1f peregrine_particle_cannon_projectile.DamageRadius = 3f peregrine_particle_cannon_projectile.ProjectileDamageType = DamageType.Splash - peregrine_particle_cannon_projectile.DamageProxy = 655 //peregrine_particle_cannon_radiation_cloud + //peregrine_particle_cannon_projectile.DamageProxy = 655 //peregrine_particle_cannon_radiation_cloud peregrine_particle_cannon_projectile.InitialVelocity = 500 peregrine_particle_cannon_projectile.Lifespan = .6f ProjectileDefinition.CalculateDerivedFields(peregrine_particle_cannon_projectile) diff --git a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsVehicle.scala b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsVehicle.scala index c2fee38f6..50876dd42 100644 --- a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsVehicle.scala +++ b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsVehicle.scala @@ -46,7 +46,7 @@ object GlobalDefinitionsVehicle { restriction = MaxOnly } - val controlSubsystem = List(VehicleSubsystemEntry.Controls) + val controlSubsystem: List[VehicleSubsystemEntry] = List(VehicleSubsystemEntry.Controls) fury.Name = "fury" fury.MaxHealth = 650 @@ -1162,7 +1162,7 @@ object GlobalDefinitionsVehicle { bailable = true } - val flightSubsystems = List(VehicleSubsystemEntry.Controls, VehicleSubsystemEntry.Ejection) + val flightSubsystems: List[VehicleSubsystemEntry] = List(VehicleSubsystemEntry.Controls, VehicleSubsystemEntry.Ejection) val variantConverter = new VariantVehicleConverter mosquito.Name = "mosquito" @@ -1684,20 +1684,20 @@ object GlobalDefinitionsVehicle { bailable = true } val normalSeat = new SeatDefinition() - val bfrSubsystems = List( - VehicleSubsystemEntry.BattleframeMovementServos, - VehicleSubsystemEntry.BattleframeSensorArray, - VehicleSubsystemEntry.BattleframeShieldGenerator, - VehicleSubsystemEntry.BattleframeTrunk + val bfrSubsystems: List[VehicleSubsystemEntry] = List( +// VehicleSubsystemEntry.BattleframeMovementServos, +// VehicleSubsystemEntry.BattleframeSensorArray, + VehicleSubsystemEntry.BattleframeShieldGenerator//, +// VehicleSubsystemEntry.BattleframeTrunk ) - val bfrGunnerSubsystems = List( + val bfrGunnerSubsystems: List[VehicleSubsystemEntry] = List( VehicleSubsystemEntry.BattleframeLeftArm, - VehicleSubsystemEntry.BattleframeRightArm, - VehicleSubsystemEntry.BattleframeLeftWeapon, - VehicleSubsystemEntry.BattleframeRightWeapon, - VehicleSubsystemEntry.BattleframeGunnerWeapon + VehicleSubsystemEntry.BattleframeRightArm//, +// VehicleSubsystemEntry.BattleframeLeftWeapon, +// VehicleSubsystemEntry.BattleframeRightWeapon, +// VehicleSubsystemEntry.BattleframeGunnerWeapon ) ++ bfrSubsystems - val bfrFlightSubsystems = List( + val bfrFlightSubsystems: List[VehicleSubsystemEntry] = List( VehicleSubsystemEntry.BattleframeFlightLeftArm, VehicleSubsystemEntry.BattleframeFlightRightArm, VehicleSubsystemEntry.BattleframeFlightLeftWeapon, @@ -1735,7 +1735,7 @@ object GlobalDefinitionsVehicle { aphelion_gunner.AutoPilotSpeeds = (5, 1) aphelion_gunner.Packet = battleFrameConverter aphelion_gunner.DestroyedModel = None - aphelion_gunner.destructionDelay = Some(4000L) + //aphelion_gunner.destructionDelay = Some(4000L) aphelion_gunner.JackingDuration = Array(0, 62, 60, 30) aphelion_gunner.RadiationShielding = 0.5f aphelion_gunner.DamageUsing = DamageCalculations.AgainstBfr @@ -1787,7 +1787,7 @@ object GlobalDefinitionsVehicle { colossus_gunner.AutoPilotSpeeds = (5, 1) colossus_gunner.Packet = battleFrameConverter colossus_gunner.DestroyedModel = None - colossus_gunner.destructionDelay = Some(4000L) + //colossus_gunner.destructionDelay = Some(4000L) colossus_gunner.JackingDuration = Array(0, 62, 60, 30) colossus_gunner.RadiationShielding = 0.5f colossus_gunner.DamageUsing = DamageCalculations.AgainstBfr @@ -1839,7 +1839,7 @@ object GlobalDefinitionsVehicle { peregrine_gunner.AutoPilotSpeeds = (5, 1) peregrine_gunner.Packet = battleFrameConverter peregrine_gunner.DestroyedModel = None - peregrine_gunner.destructionDelay = Some(4000L) + //peregrine_gunner.destructionDelay = Some(4000L) peregrine_gunner.JackingDuration = Array(0, 62, 60, 30) peregrine_gunner.RadiationShielding = 0.5f peregrine_gunner.DamageUsing = DamageCalculations.AgainstBfr diff --git a/src/main/scala/net/psforever/objects/vehicles/VehicleSubsystem.scala b/src/main/scala/net/psforever/objects/vehicles/VehicleSubsystem.scala index 29033e8ba..364ce5431 100644 --- a/src/main/scala/net/psforever/objects/vehicles/VehicleSubsystem.scala +++ b/src/main/scala/net/psforever/objects/vehicles/VehicleSubsystem.scala @@ -18,7 +18,7 @@ sealed abstract class VehicleSubsystemConditionModifier( ) extends IntEnumEntry object VehicleSubsystemConditionModifier extends IntEnum[VehicleSubsystemConditionModifier] { - val values = findValues + val values: IndexedSeq[VehicleSubsystemConditionModifier] = findValues case object Off extends VehicleSubsystemConditionModifier(value = 1065353216, multiplier = 0f, addend = 0) diff --git a/src/main/scala/net/psforever/objects/vehicles/control/BfrControl.scala b/src/main/scala/net/psforever/objects/vehicles/control/BfrControl.scala index 69d60d4b1..9dbc3436d 100644 --- a/src/main/scala/net/psforever/objects/vehicles/control/BfrControl.scala +++ b/src/main/scala/net/psforever/objects/vehicles/control/BfrControl.scala @@ -134,7 +134,7 @@ class BfrControl(vehicle: Vehicle) item.asInstanceOf[Tool], dimorph.transform(Handiness.Generic).asInstanceOf[ToolDefinition] ) - case _ => ; //no dimorphic entry; place as-is + case _ => () //no dimorphic entry; place as-is } val guid0 = PlanetSideGUID(0) //if the weapon arm is disabled, enable it for later (makes life easy) @@ -173,7 +173,8 @@ class BfrControl(vehicle: Vehicle) override def dismountCleanup(seatBeingDismounted: Int, player: Player): Unit = { super.dismountCleanup(seatBeingDismounted, player) if (!vehicle.Seats.values.exists(_.isOccupied)) { - vehicle.Subsystems(VehicleSubsystemEntry.BattleframeShieldGenerator) match { + vehicle + .Subsystems(VehicleSubsystemEntry.BattleframeShieldGenerator) match { case Some(subsys) => if (vehicle.Shields > 0) { vehicleSubsystemMessages( @@ -190,7 +191,7 @@ class BfrControl(vehicle: Vehicle) } ) } - case _ => ; + case _ => () } } } @@ -203,7 +204,7 @@ class BfrControl(vehicle: Vehicle) if !subsys.Enabled && vehicle.Shields > 0 && subsys.Enabled_=(state = true) => //if the shield is damaged, it does not turn on until the damaged is cleared vehicleSubsystemMessages(subsys.changedMessages(vehicle)) - case _ => ; + case _ => () } } } @@ -425,9 +426,9 @@ class BfrControl(vehicle: Vehicle) zone.id, VehicleAction.GenericObjectAction(doNotSendTo, useThisGuid, action) ) - case _ => ; + case _ => () } - case _ => ; + case _ => () } } } @@ -602,7 +603,7 @@ class BfrControl(vehicle: Vehicle) ) ) } - case _ => ; + case _ => () } } } diff --git a/src/main/scala/net/psforever/objects/vehicles/control/BfrFlightControl.scala b/src/main/scala/net/psforever/objects/vehicles/control/BfrFlightControl.scala index 144ecb7d9..78d9e6387 100644 --- a/src/main/scala/net/psforever/objects/vehicles/control/BfrFlightControl.scala +++ b/src/main/scala/net/psforever/objects/vehicles/control/BfrFlightControl.scala @@ -58,20 +58,20 @@ class BfrFlightControl(vehicle: Vehicle) case Some(drain) if localFlyingValue.isEmpty => //shields off disableShield() - vehicle.Subsystems(VehicleSubsystemEntry.BattleframeShieldGenerator).get.Enabled = false + vehicle.Subsystems(VehicleSubsystemEntry.BattleframeShieldGenerator).map(s => s.Enabled = false) vehicle.Shields -= drain showShieldCharge() case None if localFlyingValue.isEmpty => //shields off disableShield() - vehicle.Subsystems(VehicleSubsystemEntry.BattleframeShieldGenerator).get.Enabled = false + vehicle.Subsystems(VehicleSubsystemEntry.BattleframeShieldGenerator).map(s => s.Enabled = false) case Some(drain) => vehicle.Shields -= drain showShieldCharge() case _ => ; } } - if (vehicle.Subsystems(VehicleSubsystemEntry.BattleframeFlightPod).get.Jammed) { + if (vehicle.Subsystems(VehicleSubsystemEntry.BattleframeFlightPod).exists(_.Jammed)) { } @@ -79,7 +79,7 @@ class BfrFlightControl(vehicle: Vehicle) if (flying.nonEmpty) { flying = None vehicle.Flying = None - vehicle.Subsystems(VehicleSubsystemEntry.BattleframeShieldGenerator).get.Enabled = true + vehicle.Subsystems(VehicleSubsystemEntry.BattleframeShieldGenerator).map(s => s.Enabled = true) if (vehicle.Shields > 0) { enableShield() } diff --git a/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala b/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala index e6112726b..cc6884073 100644 --- a/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala +++ b/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala @@ -354,7 +354,7 @@ object ResolutionCalculations { { data.cause.source.DamageToBattleframeOnly || data.cause.source.DamageToVehicleOnly || - !obj.Subsystems(VehicleSubsystemEntry.BattleframeShieldGenerator).get.Enabled || + !obj.Subsystems(VehicleSubsystemEntry.BattleframeShieldGenerator).exists(_.Enabled) || obj.Shields == 0 } ) From 662844b8454dd8a04c89e4c35ca0668c7c50fda7 Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Thu, 31 Jul 2025 01:12:28 -0400 Subject: [PATCH 17/19] aded an early test to determine if player account database is active (#1281) --- .../net/psforever/actors/net/LoginActor.scala | 371 +++++++++++------- 1 file changed, 228 insertions(+), 143 deletions(-) diff --git a/src/main/scala/net/psforever/actors/net/LoginActor.scala b/src/main/scala/net/psforever/actors/net/LoginActor.scala index 4b9a87418..5eb8bb14e 100644 --- a/src/main/scala/net/psforever/actors/net/LoginActor.scala +++ b/src/main/scala/net/psforever/actors/net/LoginActor.scala @@ -24,7 +24,6 @@ import org.joda.time.LocalDateTime import scala.collection.mutable import scala.concurrent.Future import scala.concurrent.duration._ -import scala.util.matching.Regex import scala.util.{Failure, Success} object LoginActor { @@ -33,94 +32,31 @@ object LoginActor { private case object UpdateServerList extends Command final case class ReceptionistListing(listing: Receptionist.Listing) extends Command -} -class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], connectionId: String, sessionId: Long) - extends Actor - with MDCContextAware { - - import scala.concurrent.ExecutionContext.Implicits.global - - val usernameRegex: Regex = """[A-Za-z0-9]{3,}""".r - - var leftRef: ActorRef = Default.Actor - var rightRef: ActorRef = Default.Actor - var accountIntermediary: ActorRef = Default.Actor - var sockets: typed.ActorRef[SocketPane.Command] = Default.typed.Actor - - var updateServerListTask: Cancellable = Default.Cancellable - - var ipAddress: String = "" - var hostName: String = "" - var canonicalHostName: String = "" - var port: Int = 0 - - val serverName: String = Config.app.world.serverName - val gameTestServerAddress = new InetSocketAddress(InetAddress.getByName(Config.app.public), Config.app.world.port) - - private val bcryptRounds = 12 - - ServiceManager.serviceManager ! Lookup("accountIntermediary") - ServiceManager.receptionist ! Receptionist.Find(SocketPane.SocketPaneKey, context.self) - - override def postStop(): Unit = { - if (updateServerListTask != null) - updateServerListTask.cancel() - } - - def receive: Receive = { - case ServiceManager.LookupResult("accountIntermediary", endpoint) => - accountIntermediary = endpoint - - case SocketPane.SocketPaneKey.Listing(listings) => - sockets = listings.head - - case ReceiveIPAddress(address) => - ipAddress = address.Address - hostName = address.HostName - canonicalHostName = address.CanonicalHostName - port = address.Port - - case LoginActor.UpdateServerList => - updateServerList() - - case packet: PlanetSideGamePacket => - handleGamePkt(packet) - - case SocketPane.NextPort(_, _, portNum) => - val address = gameTestServerAddress.getAddress.getHostAddress - log.info(s"Connecting to ${address.toLowerCase}: $portNum ...") - val response = ConnectToWorldMessage(serverName, address, portNum) - middlewareActor ! MiddlewareActor.Send(response) - middlewareActor ! MiddlewareActor.Close() - - case default => - failWithError(s"Invalid packet class received: $default") - } - - def handleGamePkt(pkt: PlanetSideGamePacket): Unit = - pkt match { - case LoginMessage(majorVersion, minorVersion, buildDate, username, password, token, revision) => - // TODO: prevent multiple LoginMessages from being processed in a row!! We need a state machine - val clientVersion = s"Client Version: $majorVersion.$minorVersion.$revision, $buildDate" - if (token.isDefined) - log.debug(s"New login UN:$username Token:${token.get}. $clientVersion") - else { - log.debug(s"New login UN:$username. $clientVersion") - } - requestAccountLogin(username, password, token) - - case ConnectToWorldRequestMessage(name, _, _, _, _, _, _, _) => - log.info(s"Request to connect to world '$name' ...") - sockets ! SocketPane.GetNextPort("world", context.self) - - case _ => - log.warning(s"Unhandled GamePacket $pkt") + /** + * What does a token do? + * No one knows. + * @return a 32-bit ascii string + */ + private def generateToken(): String = { + val r = new scala.util.Random + val sb = new mutable.StringBuilder + for (_ <- 1 to 31) { + sb.append(r.nextPrintableChar()) } + sb.toString + } - // generates a password from username and password combination - // mimics the process the launcher follows and hashes the password salted by the username - def generateNewPassword(username: String, password: String): String = { + /** + * Generates a new password from username and password combination, + * hashing the initial password when salted by the username, + * mimicking the process the launcher follows. + * @param username part of the original details + * @param password part of the original details + * @param rounds number of times cryptographic mutation occurs + * @return new password + */ + private def generateNewPassword(username: String, password: String, rounds: Int): String = { // salt password hash with username (like the launcher does) (username + password) val saltedPassword = username.concat(password) // https://stackoverflow.com/a/46332228 @@ -129,20 +65,178 @@ class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], conne .digest(saltedPassword.getBytes("UTF-8")) .map("%02x".format(_)).mkString // bcrypt hash for DB storage - val bcryptedPassword = hashedPassword.bcryptBounded(bcryptRounds) + val bcryptedPassword = hashedPassword.bcryptBounded(rounds) bcryptedPassword } - def requestAccountLogin(username: String, passwordOpt: Option[String], tokenOpt: Option[String]): Unit = { - tokenOpt match { - case Some(token) => accountLoginWithToken(token) - case None => accountLogin(username, passwordOpt.getOrElse("")) + /** + * Remove flavor from the server name that should not show up in the log. + * @param name original name + * @return sanitized name + */ + private def sanitizeServerName(name: String): String = { + //remove color codes from the server name - look for '\\#' followed by six characters or numbers + name.replaceAll("\\\\#[\\da-fA-F]{6}","") + } +} + +class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], connectionId: String, sessionId: Long) + extends Actor + with MDCContextAware { + import scala.concurrent.ExecutionContext.Implicits.global + + //private val usernameRegex: Regex = """[A-Za-z\d]{3,}""".r might be useful one day + private var accountIntermediary: ActorRef = Default.Actor + private var sockets: typed.ActorRef[SocketPane.Command] = Default.typed.Actor + + private var updateServerListTask: Cancellable = Default.Cancellable + + private var ipAddress: String = "" + private var hostName: String = "" + private var canonicalHostName: String = "" + private var port: Int = 0 + + private val serverName: String = Config.app.world.serverName + private val gameTestServerAddress = new InetSocketAddress(InetAddress.getByName(Config.app.public), Config.app.world.port) + + private val bcryptRounds = 12 + + override def preStart(): Unit = { + super.preStart() + ServiceManager.serviceManager ! Lookup("accountIntermediary") + ServiceManager.receptionist ! Receptionist.Find(SocketPane.SocketPaneKey, context.self) + } + + override def postStop(): Unit = { + if (updateServerListTask != null) + updateServerListTask.cancel() + } + + def receive: Receive = beforeLoginBehavior + + private def persistentSetupMixinBehavior: Receive = { + case ServiceManager.LookupResult("accountIntermediary", endpoint) => + accountIntermediary = endpoint + + case SocketPane.SocketPaneKey.Listing(listings) => + sockets = listings.head + } + + private def idlingBehavior: Receive = persistentSetupMixinBehavior.orElse { + case _ => () + } + + private def beforeLoginBehavior: Receive = persistentSetupMixinBehavior.orElse { + case ReceiveIPAddress(address) => + ipAddress = address.Address + hostName = address.HostName + canonicalHostName = address.CanonicalHostName + port = address.Port + context.become(idlingBehavior) + runLoginTest() + + case _ => () + } + + private def accountLoginBehavior: Receive = persistentSetupMixinBehavior.orElse { + case packet: PlanetSideGamePacket => + handleGamePktDuringLogin(packet) + + case default => + failWithError(s"Invalid packet class received: $default") + } + + private def displayingServerListBehavior: Receive = persistentSetupMixinBehavior.orElse { + case packet: PlanetSideGamePacket => + handleGamePktDuringWorldSelect(packet) + + case LoginActor.UpdateServerList => + updateServerList() + + case SocketPane.NextPort(_, _, portNum) => + val address = gameTestServerAddress.getAddress.getHostAddress + log.info(s"Connecting to ${address.toLowerCase}: $portNum ...") + val response = ConnectToWorldMessage(serverName, address, portNum) + context.become(idlingBehavior) + middlewareActor ! MiddlewareActor.Send(response) + middlewareActor ! MiddlewareActor.Close() + + case default => + failWithError(s"Invalid packet class received: $default") + } + + private def waitingForServerTransferBehavior: Receive = persistentSetupMixinBehavior.orElse { + case SocketPane.NextPort(_, _, portNum) => + val address = gameTestServerAddress.getAddress.getHostAddress + log.info(s"Connecting to ${address.toLowerCase}: $portNum ...") + val response = ConnectToWorldMessage(serverName, address, portNum) + context.become(idlingBehavior) + middlewareActor ! MiddlewareActor.Send(response) + middlewareActor ! MiddlewareActor.Close() + + case _ => () + } + + private def handleGamePktDuringLogin(pkt: PlanetSideGamePacket): Unit = { + pkt match { + case LoginMessage(majorVersion, minorVersion, buildDate, username, _, Some(token), revision) => + val clientVersion = s"Client Version: $majorVersion.$minorVersion.$revision, $buildDate" + log.debug(s"New login UN:$username Token:$token. $clientVersion") + context.become(idlingBehavior) + accountLoginWithToken(token) + + case LoginMessage(majorVersion, minorVersion, buildDate, username, password, None, revision) => + val clientVersion = s"Client Version: $majorVersion.$minorVersion.$revision, $buildDate" + log.debug(s"New login UN:$username. $clientVersion") + context.become(idlingBehavior) + accountLogin(username, password.getOrElse("")) + + case _ => + log.warning(s"Unhandled GamePacket $pkt") } } - def accountLogin(username: String, password: String): Unit = { + private def handleGamePktDuringWorldSelect(pkt: PlanetSideGamePacket): Unit = { + pkt match { + case ConnectToWorldRequestMessage(name, _, _, _, _, _, _, _) => + val sanitizedName = LoginActor.sanitizeServerName(name) + log.info(s"Request to connect to world '$sanitizedName' ...") + context.become(waitingForServerTransferBehavior) + sockets ! SocketPane.GetNextPort("world", context.self) + + case _ => + log.warning(s"Unhandled GamePacket $pkt") + } + } + + private def runLoginTest(): Unit = { import ctx._ - val newToken = this.generateToken() + val result = for { + accountsExact <- ctx.run(query[persistence.Account].filter(_.username == lift("PSForever"))) + accountOption <- accountsExact.headOption match { + case Some(account) => + Future.successful(Some(account)) + case None => + Future.successful(None) + } + } yield accountOption + + result.onComplete { + case Success(Some(_)) => + context.become(accountLoginBehavior) // account found + case Success(None) => + middlewareActor ! MiddlewareActor.Send(DisconnectMessage("Character database not found; stopping ...")) + middlewareActor ! MiddlewareActor.Close() + case Failure(e) => + log.error(e.getMessage) + middlewareActor ! MiddlewareActor.Send(DisconnectMessage("Encountered login error; stopping ...")) + middlewareActor ! MiddlewareActor.Close() + } + } + + private def accountLogin(username: String, password: String): Unit = { + import ctx._ + val newToken = LoginActor.generateToken() val result = for { // backwards compatibility: prefer exact match first, then try lowercase accountsExact <- ctx.run(query[persistence.Account].filter(_.username == lift(username))) @@ -160,7 +254,7 @@ class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], conne case None => if (Config.app.login.createMissingAccounts) { // generate bcrypted passwords - val bcryptedPassword = generateNewPassword(username, password) + val bcryptedPassword = LoginActor.generateNewPassword(username, password, bcryptRounds) val passhash = password.bcryptBounded(bcryptRounds) // save bcrypted password hash to DB ctx.run( @@ -201,7 +295,7 @@ class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], conne if (account.password == "") { // generate bcrypted password // use username as provided by the user (db entry could be wrong), that is the way the launcher does it - val bcryptedPassword = generateNewPassword(username, password) + val bcryptedPassword = LoginActor.generateNewPassword(username, password, bcryptRounds) // update account, set password ctx.run( query[persistence.Account] @@ -210,31 +304,33 @@ class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], conne ) } loginSuccessfulResponse(username, newToken) + context.become(displayingServerListBehavior) updateServerListTask = context.system.scheduler.scheduleWithFixedDelay(0 seconds, 5 seconds, self, LoginActor.UpdateServerList) future case (_, false) => - loginPwdFailureResponse(username, newToken) - Future.successful(None) + loginFailurePasswordResponse(username, newToken) + loginFailureAction() case (true, _) => loginAccountFailureResponse(username, newToken) - Future.successful(None) + loginFailureAction() } - case None => Future.successful(None) + case None => + loginFailureAction() } } yield login result.onComplete { - case Success(_) => + case Success(_) => () case Failure(e) => log.error(e.getMessage) } } - def accountLoginWithToken(token: String): Unit = { + private def accountLoginWithToken(token: String): Unit = { import ctx._ - val newToken = this.generateToken() + val newToken = LoginActor.generateToken() val result = for { accountsExact <- ctx.run(query[persistence.Account].filter(_.token.getOrNull == lift(token))) accountOption <- accountsExact.headOption match { @@ -267,33 +363,35 @@ class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], conne _.port -> lift(port) ) ) - loginSuccessfulResponseToken(account.username, token, newToken) + loginSuccessfulResponseWithToken(account.username, token, newToken) + context.become(displayingServerListBehavior) updateServerListTask = context.system.scheduler.scheduleWithFixedDelay(0 seconds, 5 seconds, self, LoginActor.UpdateServerList) future case (_, false) => loginFailureResponseToken(account.username, token, newToken) - Future.successful(None) + loginFailureAction() case (true, _) => loginAccountFailureResponseToken(account.username, token, newToken) - Future.successful(None) + loginFailureAction() } - case None => Future.successful(None) + case None => + loginFailureAction() } } yield login result.onComplete { - case Success(_) => + case Success(_) => () case Failure(e) => log.error(e.getMessage) } } - def loginSuccessfulResponse(username: String, newToken: String): Unit = { + private def loginSuccessfulResponse(username: String, token: String): Unit = { middlewareActor ! MiddlewareActor.Send( LoginRespMessage( - newToken, + token, LoginError.Success, StationError.AccountActive, StationSubscriptionStatus.Active, @@ -304,26 +402,21 @@ class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], conne ) } - def loginSuccessfulResponseToken(username: String, token: String, newToken: String): Unit = { - log.info(s"User $username logged in unsing token $token") - middlewareActor ! MiddlewareActor.Send( - LoginRespMessage( - newToken, - LoginError.Success, - StationError.AccountActive, - StationSubscriptionStatus.Active, - 0, - username, - 10001 - ) - ) + private def loginSuccessfulResponseWithToken(username: String, token: String, newToken: String): Unit = { + log.info(s"User $username logged in using token $token") + loginSuccessfulResponse(username, newToken) } - def loginPwdFailureResponse(username: String, newToken: String): Unit = { + private def loginFailureAction(): Future[Any] = { + context.become(accountLoginBehavior) + Future.successful(None) + } + + private def loginFailurePasswordResponse(username: String, token: String): Unit = { log.warning(s"Failed login to account $username") middlewareActor ! MiddlewareActor.Send( LoginRespMessage( - newToken, + token, LoginError.BadUsernameOrPassword, StationError.AccountActive, StationSubscriptionStatus.Active, @@ -334,7 +427,7 @@ class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], conne ) } - def loginFailureResponseToken(token: String, newToken: String): Unit = { + private def loginFailureResponseToken(token: String, newToken: String): Unit = { log.warning(s"Failed login using unknown token $token") middlewareActor ! MiddlewareActor.Send( LoginRespMessage( @@ -349,7 +442,7 @@ class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], conne ) } - def loginFailureResponseTokenExpired(token: String, newToken: String): Unit = { + private def loginFailureResponseTokenExpired(token: String, newToken: String): Unit = { log.warning(s"Failed login using expired token $token") middlewareActor ! MiddlewareActor.Send( LoginRespMessage( @@ -364,11 +457,11 @@ class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], conne ) } - def loginFailureResponse(username: String, newToken: String): Unit = { + private def loginFailureResponse(username: String, token: String): Unit = { log.warning(s"DB problem username: $username") middlewareActor ! MiddlewareActor.Send( LoginRespMessage( - newToken, + token, LoginError.unk1, StationError.AccountActive, StationSubscriptionStatus.Active, @@ -379,7 +472,7 @@ class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], conne ) } - def loginFailureResponseToken(username: String, token: String, newToken: String): Unit = { + private def loginFailureResponseToken(username: String, token: String, newToken: String): Unit = { log.warning(s"DB problem username $username token: $token") middlewareActor ! MiddlewareActor.Send( LoginRespMessage( @@ -394,11 +487,11 @@ class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], conne ) } - def loginAccountFailureResponse(username: String, newToken: String): Unit = { + private def loginAccountFailureResponse(username: String, token: String): Unit = { log.warning(s"Account $username inactive") middlewareActor ! MiddlewareActor.Send( LoginRespMessage( - newToken, + token, LoginError.BadUsernameOrPassword, StationError.AccountClosed, StationSubscriptionStatus.Active, @@ -409,7 +502,7 @@ class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], conne ) } - def loginAccountFailureResponseToken(username: String, token: String, newToken: String): Unit = { + private def loginAccountFailureResponseToken(username: String, token: String, newToken: String): Unit = { log.warning(s"Account $username inactive token: $token ") middlewareActor ! MiddlewareActor.Send( LoginRespMessage( @@ -424,16 +517,8 @@ class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], conne ) } - def generateToken(): String = { - val r = new scala.util.Random - val sb = new mutable.StringBuilder - for (_ <- 1 to 31) { - sb.append(r.nextPrintableChar()) - } - sb.toString - } - - def updateServerList(): Unit = { + private def updateServerList(): Unit = { + //todo list of game servers from database, eventually, which is a separation of game server from login server middlewareActor ! MiddlewareActor.Send( VNLWorldStatusMessage( "Welcome to PlanetSide! ", @@ -450,7 +535,7 @@ class LoginActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], conne ) } - def failWithError(error: String): Unit = { + private def failWithError(error: String): Unit = { log.error(error) middlewareActor ! MiddlewareActor.Close() } From c1d9f679b2adb979ba6f124101ee2f32d20633da Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Thu, 31 Jul 2025 01:13:32 -0400 Subject: [PATCH 18/19] No Safe Spaces (#1283) * local zone maintains information about weapon fire capability per faction * map reload by faction to represent a change in weapons fire permissions via LMM --- .../actors/session/csr/ChatLogic.scala | 130 ++++++++++++++++++ .../session/normal/LocalHandlerLogic.scala | 23 +++- .../actors/session/spectator/ChatLogic.scala | 2 +- .../session/support/ChatOperations.scala | 26 +++- .../session/support/ZoningOperations.scala | 4 +- .../net/psforever/objects/zones/Zone.scala | 46 +++++++ .../services/local/LocalService.scala | 8 ++ .../services/local/LocalServiceMessage.scala | 1 + .../services/local/LocalServiceResponse.scala | 2 + 9 files changed, 235 insertions(+), 7 deletions(-) diff --git a/src/main/scala/net/psforever/actors/session/csr/ChatLogic.scala b/src/main/scala/net/psforever/actors/session/csr/ChatLogic.scala index 24c5c8851..f90de34da 100644 --- a/src/main/scala/net/psforever/actors/session/csr/ChatLogic.scala +++ b/src/main/scala/net/psforever/actors/session/csr/ChatLogic.scala @@ -18,6 +18,7 @@ import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.chat.{ChatChannel, DefaultChannel, SpectatorChannel} import net.psforever.types.ChatMessageType.{CMT_TOGGLESPECTATORMODE, CMT_TOGGLE_GM} import net.psforever.types.{ChatMessageType, PlanetSideEmpire} +import net.psforever.zones.Zones import scala.util.Success @@ -225,6 +226,7 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext case "hidespectators" => customCommandHideSpectators() case "sayspectator" => customCommandSpeakAsSpectator(params, message) case "setempire" => customCommandSetEmpire(params) + case "weaponlock" => customCommandZoneWeaponUnlock(session, params) case _ => // command was not handled sendResponse( @@ -411,6 +413,134 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext } } + def customCommandZoneWeaponUnlock(session: Session, params: Seq[String]): Boolean = { + val usageMessage: Boolean = params.exists(_.matches("--help")) || params.exists(_.matches("-h")) + val formattedParams = ops.cliCommaSeparatedParams(params) + //handle params + val (zoneList, verifiedZones, factionList, verifiedFactions, stateOpt) = (formattedParams.headOption, formattedParams.lift(1), formattedParams.lift(2)) match { + case _ if usageMessage => + (Nil, Nil, Nil, Nil, None) + + case (None, None, None) => + ( + Seq(session.zone.id), + Seq(session.zone), + PlanetSideEmpire.values.map(_.toString()).toSeq, + PlanetSideEmpire.values.toSeq, + Some(true) + ) + + case (Some(zoneOrFaction), Some(factionOrZone), stateOpt) => + val factionOrZoneSplit = factionOrZone.split(",").toSeq + val zoneOrFactionSplit = zoneOrFaction.split(",").toSeq + val tryToFactions = factionOrZoneSplit.flatten(s => ops.captureBaseParamFaction(session, Some(s))) + if (tryToFactions.isEmpty) { + ( + factionOrZoneSplit, + customCommandZoneParse(factionOrZoneSplit), + zoneOrFactionSplit, + zoneOrFactionSplit.flatten(s => ops.captureBaseParamFaction(session, Some(s))), + customCommandOnOffStateOrNone(stateOpt) + ) + } else { + ( + zoneOrFactionSplit, + customCommandZoneParse(zoneOrFactionSplit), + factionOrZoneSplit, + tryToFactions, + customCommandOnOffStateOrNone(stateOpt) + ) + } + + case (Some(zoneOrFaction), stateOpt, None) => + val zoneOrFactionSplit = zoneOrFaction.split(",").toSeq + val tryToFactions = zoneOrFactionSplit.flatten(s => ops.captureBaseParamFaction(session, Some(s))) + if (tryToFactions.isEmpty) { + ( + zoneOrFactionSplit, + customCommandZoneParse(zoneOrFactionSplit), + PlanetSideEmpire.values.map(_.toString()).toSeq, + PlanetSideEmpire.values.toSeq, + customCommandOnOffStateOrNone(stateOpt) + ) + } else { + ( + Seq(session.zone.id), + Seq(session.zone), + zoneOrFactionSplit, + tryToFactions, + customCommandOnOffStateOrNone(stateOpt) + ) + } + + case (stateOpt, None, None) => + ( + Seq(session.zone.id), + Seq(session.zone), + PlanetSideEmpire.values.map(_.toString()).toSeq, + PlanetSideEmpire.values.toSeq, + customCommandOnOffState(stateOpt) + ) + } + //resolve + if (usageMessage) { + sendResponse(ChatMsg(ChatMessageType.UNK_227, "!weaponlock [zone[,...]] [faction[,...]] [o[n]|of[f]]")) + } else if (zoneList.isEmpty || verifiedZones.isEmpty || zoneList.size != verifiedZones.size) { + sendResponse(ChatMsg(ChatMessageType.UNK_227, "some zones can not be verified")) + } else if (factionList.isEmpty || verifiedFactions.isEmpty || factionList.size != verifiedFactions.size) { + sendResponse(ChatMsg(ChatMessageType.UNK_227, "some factions can not be verified")) + } else if (stateOpt.isEmpty) { + sendResponse(ChatMsg(ChatMessageType.UNK_227, "state must be on or off")) + } else { + val state = !stateOpt.get + verifiedZones.foreach { zone => + val events = zone.AvatarEvents + val zoneId = zone.id + //val reloadZoneMsg = AvatarAction.ReloadZone(zone) + zone + .UpdateLiveFireAllowed(state, verifiedFactions) + .foreach { + case (_, false, _) => () + case (faction, true, _) => + //events ! AvatarServiceMessage(s"$faction", reloadZoneMsg) + } + } + } + true + } + + private def customCommandOnOffStateOrNone(stateOpt: Option[String]): Option[Boolean] = { + stateOpt match { + case None => + Some(true) + case _ => + customCommandOnOffState(stateOpt) + } + } + + private def customCommandOnOffState(stateOpt: Option[String]): Option[Boolean] = { + stateOpt match { + case Some("o") | Some("on") => + Some(false) + case Some("of") | Some("off") => + Some(true) + case _ => + None + } + } + + def customCommandZoneParse(potentialZones: Seq[String]): Seq[Zone] = { + potentialZones.flatten { potentialZone => + if (potentialZone.toIntOption.nonEmpty) { + val xInt = potentialZone.toInt + Zones.zones.find(_.Number == xInt) + } else { + Zones.zones.find(z => z.id.equals(potentialZone)) + } + } + } + + override def stop(): Unit = { super.stop() seeSpectatorsIn.foreach(_ => customCommandHideSpectators()) diff --git a/src/main/scala/net/psforever/actors/session/normal/LocalHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/normal/LocalHandlerLogic.scala index 3b7538a5a..1b1764743 100644 --- a/src/main/scala/net/psforever/actors/session/normal/LocalHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/LocalHandlerLogic.scala @@ -8,9 +8,9 @@ import net.psforever.objects.serverobject.doors.Door import net.psforever.objects.vehicles.MountableWeapons import net.psforever.objects.{BoomerDeployable, ExplosiveDeployable, TelepadDeployable, Tool, TurretDeployable} import net.psforever.packet.game.{ChatMsg, DeployableObjectsInfoMessage, GenericActionMessage, GenericObjectActionMessage, GenericObjectStateMsg, HackMessage, HackState, HackState1, InventoryStateMessage, ObjectAttachMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectDetachMessage, OrbitalShuttleTimeMsg, PadAndShuttlePair, PlanetsideAttributeMessage, ProximityTerminalUseMessage, SetEmpireMessage, TriggerEffectMessage, TriggerSoundMessage, TriggeredSound, VehicleStateMessage} -import net.psforever.services.Service +import net.psforever.services.{InterstellarClusterService, Service} import net.psforever.services.local.LocalResponse -import net.psforever.types.{ChatMessageType, PlanetSideGUID} +import net.psforever.types.{ChatMessageType, PlanetSideGUID, SpawnGroup} object LocalHandlerLogic { def apply(ops: SessionLocalHandlers): LocalHandlerLogic = { @@ -240,6 +240,25 @@ class LocalHandlerLogic(val ops: SessionLocalHandlers, implicit val context: Act sendResponse(InventoryStateMessage(weapon.AmmoSlot.Box.GUID, weapon.GUID, weapon.Magazine)) } + case LocalResponse.ForceZoneChange(zone) => + //todo we might be able to piggyback this for squad recalls later + if(session.zone eq zone) { + sessionLogic.zoning.zoneReload = true + zone.AvatarEvents ! Service.Leave() + zone.LocalEvents ! Service.Leave() + zone.VehicleEvents ! Service.Leave() + zone.AvatarEvents ! Service.Join(player.Name) //must manually restore this subscriptions + sessionLogic.zoning.spawn.handleNewPlayerLoaded(player) //will restart subscriptions and dispatch a LoadMapMessage + } else { + import akka.actor.typed.scaladsl.adapter._ + sessionLogic.cluster ! InterstellarClusterService.GetRandomSpawnPoint( + zone.Number, + player.Faction, + Seq(SpawnGroup.Facility, SpawnGroup.Tower, SpawnGroup.AMS), + context.self + ) + } + case _ => () } } diff --git a/src/main/scala/net/psforever/actors/session/spectator/ChatLogic.scala b/src/main/scala/net/psforever/actors/session/spectator/ChatLogic.scala index ae2857066..c17b2fdc1 100644 --- a/src/main/scala/net/psforever/actors/session/spectator/ChatLogic.scala +++ b/src/main/scala/net/psforever/actors/session/spectator/ChatLogic.scala @@ -140,7 +140,7 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext case _ => ("", Seq("")) } command match { - case "list" => ops.customCommandList(session, params, message) + case "list" => ops.customCommandList(session, params.toSeq, message) case "nearby" => ops.customCommandNearby(session) case "loc" => ops.customCommandLoc(session, message) case _ => diff --git a/src/main/scala/net/psforever/actors/session/support/ChatOperations.scala b/src/main/scala/net/psforever/actors/session/support/ChatOperations.scala index 399fede66..7827cd234 100644 --- a/src/main/scala/net/psforever/actors/session/support/ChatOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ChatOperations.scala @@ -17,7 +17,7 @@ import org.log4s.Logger import java.util.concurrent.{Executors, TimeUnit} import scala.annotation.unused -import scala.collection.{Seq, mutable} +import scala.collection.mutable import scala.concurrent.duration._ // import net.psforever.actors.zone.BuildingActor @@ -1034,7 +1034,7 @@ class ChatOperations( } } - private def captureBaseParamFaction( + def captureBaseParamFaction( @unused session: Session, token: Option[String] ): Option[PlanetSideEmpire.Value] = { @@ -1307,6 +1307,28 @@ class ChatOperations( str.replaceAll("\\s+", " ").trim.split("\\s").toList.filter(!_.equals("")) } + def cliCommaSeparatedParams(params: Seq[String]): Seq[String] = { + var len = 0 + var appendNext = false + var formattedParams: Seq[String] = Seq() + params.foreach { + case "," => + appendNext = true + case param if appendNext || param.startsWith(",") => + formattedParams = formattedParams.slice(0, len - 1) :+ formattedParams(len - 1) + "," + param.replaceAll(",", "") + appendNext = param.endsWith(",") + case param if param.endsWith(",") => + formattedParams = formattedParams :+ param.take(param.length-1) + len += 1 + appendNext = true + case param => + formattedParams = formattedParams :+ param + len += 1 + appendNext = false + } + formattedParams + } + def commandIncomingSend(message: ChatMsg): Unit = { sendResponse(message) } 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 c2404d291..7f9aa7315 100644 --- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala @@ -2160,8 +2160,8 @@ class ZoningOperations( tplayer.avatar = avatar session = session.copy(player = tplayer) //LoadMapMessage causes the client to send BeginZoningMessage, eventually leading to SetCurrentAvatar - val weaponsEnabled = !(mapName.equals("map11") || mapName.equals("map12") || mapName.equals("map13")) - sendResponse(LoadMapMessage(mapName, id, 40100, 25, weaponsEnabled, map.checksum)) + //val weaponsEnabled = !(mapName.equals("map11") || mapName.equals("map12") || mapName.equals("map13")) + sendResponse(LoadMapMessage(mapName, id, 40100, 25, zone.LiveFireAllowed(tplayer.Faction), map.checksum)) if (isAcceptableNextSpawnPoint) { //important! the LoadMapMessage must be processed by the client before the avatar is created player.allowInteraction = true diff --git a/src/main/scala/net/psforever/objects/zones/Zone.scala b/src/main/scala/net/psforever/objects/zones/Zone.scala index cbf828d07..4deac1700 100644 --- a/src/main/scala/net/psforever/objects/zones/Zone.scala +++ b/src/main/scala/net/psforever/objects/zones/Zone.scala @@ -182,6 +182,12 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) { */ private var vehicleEvents: ActorRef = Default.Actor + /** + * Is any player permitted to engage in weapons discharge in this zone? + */ + private var liveFireAllowed: mutable.HashMap[PlanetSideEmpire.Value, Boolean] = + mutable.HashMap.from(PlanetSideEmpire.values.map { f => (f, true) }) + /** * When the zone has completed initializing, fulfill this promise. * @see `init(ActorContext)` @@ -593,6 +599,46 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) { vehicleEvents = bus VehicleEvents } + + def LiveFireAllowed(): Boolean = liveFireAllowed.exists { case (_, v) => v } + + def LiveFireAllowed(faction: PlanetSideEmpire.Value): Boolean = liveFireAllowed.getOrElse(faction, false) + + def UpdateLiveFireAllowed(state: Boolean): List[(PlanetSideEmpire.Value, Boolean, Boolean)] = { + val output = liveFireAllowed.map { case (f, v) => + (f, v == state, state) + } + output.foreach { case (f, _, v) => + liveFireAllowed.update(f, v) + } + output.toList + } + + def UpdateLiveFireAllowed(state: Boolean, faction: PlanetSideEmpire.Value): List[(PlanetSideEmpire.Value, Boolean, Boolean)] = { + val output = liveFireAllowed.map { case (f, v) => + if (f == faction) { + (f, v == state, state) + } else { + (f, false, v) + } + } + liveFireAllowed.update(faction, state) + output.toList + } + + def UpdateLiveFireAllowed(state: Boolean, factions: Seq[PlanetSideEmpire.Value]): List[(PlanetSideEmpire.Value, Boolean, Boolean)] = { + val output = liveFireAllowed.map { case (f, v) => + if (factions.contains(f)) { + (f, v == state, state) + } else { + (f, false, v) + } + } + factions.foreach { f => + liveFireAllowed.update(f, state) + } + output.toList + } } /** diff --git a/src/main/scala/net/psforever/services/local/LocalService.scala b/src/main/scala/net/psforever/services/local/LocalService.scala index 91d900d3b..751dce97b 100644 --- a/src/main/scala/net/psforever/services/local/LocalService.scala +++ b/src/main/scala/net/psforever/services/local/LocalService.scala @@ -306,6 +306,14 @@ class LocalService(zone: Zone) extends Actor { LocalResponse.RechargeVehicleWeapon(vehicle_guid, weapon_guid) ) ) + case LocalAction.ForceZoneChange(zone) => + LocalEvents.publish( + LocalServiceResponse( + s"/$forChannel/Local", + Service.defaultPlayerGUID, + LocalResponse.ForceZoneChange(zone) + ) + ) case _ => ; } diff --git a/src/main/scala/net/psforever/services/local/LocalServiceMessage.scala b/src/main/scala/net/psforever/services/local/LocalServiceMessage.scala index de4545cc5..ced629458 100644 --- a/src/main/scala/net/psforever/services/local/LocalServiceMessage.scala +++ b/src/main/scala/net/psforever/services/local/LocalServiceMessage.scala @@ -138,4 +138,5 @@ object LocalAction { mountable_guid: PlanetSideGUID, weapon_guid: PlanetSideGUID ) extends Action + final case class ForceZoneChange(zone: Zone) extends Action } diff --git a/src/main/scala/net/psforever/services/local/LocalServiceResponse.scala b/src/main/scala/net/psforever/services/local/LocalServiceResponse.scala index ffb1bb3ff..8834ab197 100644 --- a/src/main/scala/net/psforever/services/local/LocalServiceResponse.scala +++ b/src/main/scala/net/psforever/services/local/LocalServiceResponse.scala @@ -6,6 +6,7 @@ import net.psforever.objects.{PlanetSideGameObject, TelepadDeployable, Vehicle} import net.psforever.objects.ce.{Deployable, DeployedItem} import net.psforever.objects.serverobject.terminals.{ProximityUnit, Terminal} import net.psforever.objects.vehicles.Utility +import net.psforever.objects.zones.Zone import net.psforever.packet.game.GenericObjectActionEnum.GenericObjectActionEnum import net.psforever.packet.game.PlanetsideAttributeEnum.PlanetsideAttributeEnum import net.psforever.packet.PlanetSideGamePacket @@ -86,4 +87,5 @@ object LocalResponse { final case class TriggerSound(sound: TriggeredSound.Value, pos: Vector3, unk: Int, volume: Float) extends Response final case class UpdateForceDomeStatus(building_guid: PlanetSideGUID, activated: Boolean) extends Response final case class RechargeVehicleWeapon(mountable_guid: PlanetSideGUID, weapon_guid: PlanetSideGUID) extends Response + final case class ForceZoneChange(zone: Zone) extends Response } From bb858203e49df5f93928f448e712ee4774ba5a4f Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Thu, 31 Jul 2025 01:14:40 -0400 Subject: [PATCH 19/19] Lukewarm Potato (#1285) * removing the cep reward for killing a flag carrier * log flag carrier kill to database as a facility capture event * need to acronym the exp event type, three characters only --- src/main/resources/application.conf | 2 +- .../actors/session/support/GeneralOperations.scala | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index 27b1e4a5b..20885b9e5 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -469,7 +469,7 @@ game { # If a player died while carrying an lattice logic unit, # and satisfies the carrying duration, # award the player who is accredited with the kill command experience. - llu-slayer-credit = 200 + llu-slayer-credit = 0 # The maximum command experience that can be earned in a facility capture based on squad size maximum-per-squad-size = [990, 1980, 3466, 4950, 6436, 7920, 9406, 10890, 12376, 13860] # When the cep has to be capped for squad size, add a small value to the capped value diff --git a/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala b/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala index 181a1e746..e72e57a56 100644 --- a/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala @@ -15,6 +15,7 @@ import net.psforever.objects.serverobject.turret.FacilityTurret import net.psforever.objects.sourcing.{DeployableSource, PlayerSource, VehicleSource} import net.psforever.objects.vehicles.Utility.InternalTelepad import net.psforever.objects.zones.blockmap.BlockMapEntity +import net.psforever.objects.zones.exp.ToDatabase import net.psforever.services.RemoverActor import net.psforever.services.local.{LocalAction, LocalServiceMessage} @@ -444,6 +445,13 @@ class GeneralOperations( attacker.Name, AvatarAction.AwardCep(attacker.CharId, Config.app.game.experience.cep.lluSlayerCredit) ) + ToDatabase.reportFacilityCapture( + attacker.CharId, + continent.Number, + llu.Owner.GUID.guid, + Config.app.game.experience.cep.lluSlayerCredit, + expType = "lsc" //three characters - "llu slayer credit" + ) } } if (!CaptureFlagManager.ReasonToLoseFlagViolently(continent, Some(guid), player)) {