From 3f1efefc208c1b1dd39e2e1da71b1ca1c7aa3cef Mon Sep 17 00:00:00 2001 From: ScrawnyRonnie Date: Tue, 30 Dec 2025 21:46:53 -0500 Subject: [PATCH] cavern lock benefits, faster capturebase all checkpoint --- .../session/normal/GalaxyHandlerLogic.scala | 7 ++ .../session/support/ChatOperations.scala | 31 +++++---- .../session/support/ZoningOperations.scala | 67 ++++++++++++++++--- .../psforever/actors/zone/BuildingActor.scala | 1 - .../net/psforever/actors/zone/ZoneActor.scala | 44 +++++++++++- .../zone/building/CavernFacilityLogic.scala | 15 ++++- .../zone/building/MajorFacilityLogic.scala | 9 +++ .../actors/zone/building/WarpGateLogic.scala | 3 - .../psforever/objects/GlobalDefinitions.scala | 2 + .../objects/equipment/EffectTarget.scala | 12 +++- .../GlobalDefinitionsMiscellaneous.scala | 9 +++ .../serverobject/structures/Building.scala | 10 ++- .../terminals/ProximityTerminalControl.scala | 42 ++++++++++++ .../objects/vital/etc/PainboxReason.scala | 15 ++++- .../local/support/HackCaptureActor.scala | 22 ++---- .../scala/net/psforever/zones/Zones.scala | 11 ++- 16 files changed, 248 insertions(+), 52 deletions(-) diff --git a/src/main/scala/net/psforever/actors/session/normal/GalaxyHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/normal/GalaxyHandlerLogic.scala index f82492cf2..fe2d4b873 100644 --- a/src/main/scala/net/psforever/actors/session/normal/GalaxyHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/GalaxyHandlerLogic.scala @@ -45,6 +45,13 @@ class GalaxyHandlerLogic(val ops: SessionGalaxyHandlers, implicit val context: A case GalaxyResponse.MapUpdate(msg) => sendResponse(msg) + import net.psforever.actors.zone.ZoneActor + import net.psforever.zones.Zones + Zones.zones.find(_.Number == msg.continent_id) match { + case Some(zone) => + zone.actor ! ZoneActor.BuildingInfoState(msg) + case None => + } case GalaxyResponse.UpdateBroadcastPrivileges(zoneId, gateMapId, fromFactions, toFactions) => val faction = player.Faction 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 cb945fd3e..3cc25a788 100644 --- a/src/main/scala/net/psforever/actors/session/support/ChatOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ChatOperations.scala @@ -368,7 +368,8 @@ class ChatOperations( case (Some(buildings), Some(faction), Some(_)) => //TODO implement timer //schedule processing of buildings with a delay - processBuildingsWithDelay(buildings, faction, 1000) { zone => + processBuildingsWithDelay(buildings, faction, 100) { zone => + zone.actor ! ZoneActor.ZoneMapUpdate() zone.actor ! ZoneActor.AssignLockedBy(zone, notifyPlayers=true) } true @@ -382,6 +383,7 @@ class ChatOperations( faction: PlanetSideEmpire.Value, delayMillis: Long )(onComplete: Zone => Unit): Unit = { + import net.psforever.objects.serverobject.structures.StructureType val buildingsToProcess = buildings.filter(b => b.CaptureTerminal.isDefined && b.Faction != faction) val iterator = buildingsToProcess.iterator val zone = buildings.head.Zone @@ -391,18 +393,23 @@ class ChatOperations( if (iterator.hasNext) { val building = iterator.next() val terminal = building.CaptureTerminal.get - val zoneActor = zone.actor - if (building.CaptureTerminalIsHacked) { - zone.LocalEvents ! LocalServiceMessage( - zone.id, - LocalAction.ResecureCaptureTerminal(terminal, PlayerSource.Nobody) - ) + if (building.BuildingType == StructureType.Tower) { + building.Actor ! BuildingActor.SetFaction(faction) + building.Actor ! BuildingActor.AmenityStateChange(terminal, Some(false)) + building.Actor ! BuildingActor.MapUpdate() } - zoneActor ! ZoneActor.ZoneMapUpdate() - building.Actor ! BuildingActor.SetFaction(faction) - building.Actor ! BuildingActor.AmenityStateChange(terminal, Some(false)) - zoneActor ! ZoneActor.ZoneMapUpdate() - } else { + else { + if (building.CaptureTerminalIsHacked) { + zone.LocalEvents ! LocalServiceMessage( + zone.id, + LocalAction.ResecureCaptureTerminal(terminal, PlayerSource.Nobody) + ) + } + building.Actor ! BuildingActor.SetFaction(faction) + building.Actor ! BuildingActor.AmenityStateChange(terminal, Some(false)) + } + } + else { handle.cancel(false) onComplete(zone) } 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 5d659bd79..bbbb3a982 100644 --- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala @@ -342,6 +342,13 @@ class ZoningOperations( sendResponse(PlanetsideAttributeMessage(targetPlayer.GUID, 19, 1)) } } + //adjust for health module benefit so overhead health bar accounts for added health + live.filter { tplayer => + tplayer.MaxHealth == 120 + } + .foreach { targetPlayer => + sendResponse(PlanetsideAttributeMessage(targetPlayer.GUID, 1, 120)) + } //load corpses in zone continent.Corpses.foreach { spawn.DepictPlayerAsCorpse @@ -2943,16 +2950,40 @@ class ZoningOperations( 0 seconds } else { //for other zones ... - //Searhus lock benefit also gives biolab faster respawn - val searhusBenefit = Zones.zones.find(_.Number == 9).exists(_.benefitRecipient == player.Faction) - //biolabs have/grant benefits - val cryoBenefit: Float = toSpawnPoint.Owner match { - case b: Building if (b.hasLatticeBenefit(LatticeBenefit.BioLaboratory) && b.virusId != 1) || - (b.BuildingType == StructureType.Facility && !b.CaptureTerminalIsHacked && searhusBenefit) => 0.5f - case _ => 1f + val spawnTimeBenefit: Float = toSpawnPoint.Owner match { + case b: Building => FasterRespawnBenefits(b) + case _ => 1f } //TODO cumulative death penalty - (toSpawnPoint.Definition.Delay.toFloat * cryoBenefit).seconds + (toSpawnPoint.Definition.Delay.toFloat * spawnTimeBenefit).seconds + } + } + + /** + * Multiple benefits can be given to an empire based on global ownership of certain zones or facility types that + * are linked to the facility being spawned at. + * @return float to potentially lower the respawn time if benefits are available + */ + def FasterRespawnBenefits(building: Building): Float = { + //Searhus lock benefit also gives biolab faster respawn + val searhusBenefit = Zones.zones.find(_.Number == 9).exists(_.benefitRecipient == player.Faction) + building match { + case b: Building + if (b.hasLatticeBenefit(LatticeBenefit.BioLaboratory) && b.virusId != 1 && + b.hasCavernLockBenefit) || + (b.BuildingType == StructureType.Facility && !b.CaptureTerminalIsHacked && + searhusBenefit && b.hasCavernLockBenefit) => + 0.3f + case b: Building + if !b.CaptureTerminalIsHacked && b.hasCavernLockBenefit && b.virusId != 1 => + 0.5f + case b: Building + if (b.hasLatticeBenefit(LatticeBenefit.BioLaboratory) && b.virusId != 1) || + (b.BuildingType == StructureType.Facility && !b.CaptureTerminalIsHacked && + searhusBenefit) => + 0.5f + case _ => + 1f } } @@ -3228,7 +3259,11 @@ class ZoningOperations( buildingType == StructureType.Bunker } .foreach { case (_, building) => - sendResponse(PlanetsideAttributeMessage(building.GUID, 67, 0 /*building.BuildingType == StructureType.Facility*/)) + if (building.hasCavernLockBenefit) { + sendResponse(PlanetsideAttributeMessage(building.GUID, 67, 1)) + } + else + sendResponse(PlanetsideAttributeMessage(building.GUID, 67, 0)) } statisticsPacketFunc() if (tplayer.ExoSuit == ExoSuitType.MAX) { @@ -3353,6 +3388,20 @@ class ZoningOperations( } }) } + nextSpawnPoint.map(_.Owner) match { + case Some(b: Building) if b.hasCavernLockBenefit => + tplayer.MaxHealth = 120 + tplayer.Health = 120 + tplayer.Zone.AvatarEvents ! AvatarServiceMessage( + tplayer.Zone.id, + AvatarAction.PlanetsideAttributeToAll(tplayer.GUID, 0, 120) + ) + tplayer.Zone.AvatarEvents ! AvatarServiceMessage( + tplayer.Zone.id, + AvatarAction.PlanetsideAttributeToAll(tplayer.GUID, 1, 120) + ) + case _ => () + } doorsThatShouldBeOpenInRange(pos, range = 100f) setAvatar = true player.allowInteraction = true diff --git a/src/main/scala/net/psforever/actors/zone/BuildingActor.scala b/src/main/scala/net/psforever/actors/zone/BuildingActor.scala index 05e135bbe..0f385dae4 100644 --- a/src/main/scala/net/psforever/actors/zone/BuildingActor.scala +++ b/src/main/scala/net/psforever/actors/zone/BuildingActor.scala @@ -167,7 +167,6 @@ object BuildingActor { val building = details.building val zone = building.Zone building.Faction = faction - zone.actor ! ZoneActor.ZoneMapUpdate() // Update entire lattice to show lattice benefits zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.SetEmpire(building.GUID, faction)) } } diff --git a/src/main/scala/net/psforever/actors/zone/ZoneActor.scala b/src/main/scala/net/psforever/actors/zone/ZoneActor.scala index d5461dc04..28a3c2015 100644 --- a/src/main/scala/net/psforever/actors/zone/ZoneActor.scala +++ b/src/main/scala/net/psforever/actors/zone/ZoneActor.scala @@ -8,7 +8,7 @@ import net.psforever.objects.serverobject.structures.{StructureType, WarpGate} import net.psforever.objects.zones.Zone import net.psforever.objects.zones.blockmap.{BlockMapEntity, SectorGroup} import net.psforever.objects.{ConstructionItem, PlanetSideGameObject, Player, Vehicle} -import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3} +import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, PlanetSideGeneratorState, Vector3} import akka.actor.typed.scaladsl.adapter._ import net.psforever.actors.zone.building.MajorFacilityLogic import net.psforever.objects.avatar.scoring.Kill @@ -18,8 +18,10 @@ import net.psforever.objects.serverobject.turret.FacilityTurret import net.psforever.objects.sourcing.SourceEntry import net.psforever.objects.vital.{InGameActivity, InGameHistory} import net.psforever.objects.zones.exp.{ExperienceCalculator, SupportExperienceCalculator} +import net.psforever.packet.game.{BuildingInfoUpdateMessage, PlanetsideAttributeMessage} import net.psforever.util.Database._ import net.psforever.persistence +import net.psforever.services.local.{LocalAction, LocalServiceMessage} import scala.collection.mutable import scala.util.{Failure, Success} @@ -80,6 +82,8 @@ object ZoneActor { final case class RewardOurSupporters(target: SourceEntry, history: Iterable[InGameActivity], kill: Kill, bep: Long) extends Command final case class AssignLockedBy(zone: Zone, notifyPlayers: Boolean) extends Command + + final case class BuildingInfoState(msg: BuildingInfoUpdateMessage) extends Command } class ZoneActor( @@ -186,7 +190,7 @@ class ZoneActor( case ZoneMapUpdate() => zone.Buildings .filter(building => - building._2.BuildingType == StructureType.Facility || building._2.BuildingType == StructureType.Tower) + building._2.BuildingType == StructureType.Facility) .values .foreach(_.Actor ! BuildingActor.MapUpdate()) Behaviors.same @@ -194,6 +198,10 @@ class ZoneActor( case AssignLockedBy(zone, notifyPlayers) => AssignLockedBy(zone, notifyPlayers) Behaviors.same + + case BuildingInfoState(msg) => + UpdateBuildingState(msg) + Behaviors.same } .receiveSignal { case (_, PostStop) => @@ -221,4 +229,36 @@ class ZoneActor( zone.benefitRecipient if (facilities.nonEmpty && notifyPlayers) { zone.NotifyContinentalLockBenefits(zone, facilities.head) } } + + def UpdateBuildingState(msg: BuildingInfoUpdateMessage): Unit = { + val buildingOpt = zone.Buildings.collectFirst { + case (_, b) if b.MapId == msg.building_map_id => b + } + buildingOpt.foreach { building => + if (msg.generator_state == PlanetSideGeneratorState.Normal && building.hasCavernLockBenefit) { + zone.LocalEvents ! LocalServiceMessage( + zone.id, + LocalAction.SendResponse(PlanetsideAttributeMessage(building.GUID, 67, 1)) + ) + } + msg.is_hacked match { + case true if building.BuildingType == StructureType.Facility && !zone.map.cavern => + zone.LocalEvents ! LocalServiceMessage( + zone.id, + LocalAction.SendResponse(PlanetsideAttributeMessage(building.GUID, 67, 0)) + ) + case false if building.hasCavernLockBenefit => + zone.LocalEvents ! LocalServiceMessage( + zone.id, + LocalAction.SendResponse(PlanetsideAttributeMessage(building.GUID, 67, 1)) + ) + case false if building.BuildingType == StructureType.Facility && !zone.map.cavern && !building.hasCavernLockBenefit => + zone.LocalEvents ! LocalServiceMessage( + zone.id, + LocalAction.SendResponse(PlanetsideAttributeMessage(building.GUID, 67, 0)) + ) + case _ => + } + } + } } diff --git a/src/main/scala/net/psforever/actors/zone/building/CavernFacilityLogic.scala b/src/main/scala/net/psforever/actors/zone/building/CavernFacilityLogic.scala index 30f41b25a..ebfb38a80 100644 --- a/src/main/scala/net/psforever/actors/zone/building/CavernFacilityLogic.scala +++ b/src/main/scala/net/psforever/actors/zone/building/CavernFacilityLogic.scala @@ -4,8 +4,8 @@ package net.psforever.actors.zone.building import akka.actor.typed.Behavior import akka.actor.typed.scaladsl.{ActorContext, Behaviors} import net.psforever.actors.commands.NtuCommand -import net.psforever.actors.zone.{BuildingActor, BuildingControlDetails} -import net.psforever.objects.serverobject.structures.{Amenity, Building} +import net.psforever.actors.zone.{BuildingActor, BuildingControlDetails, ZoneActor} +import net.psforever.objects.serverobject.structures.{Amenity, Building, StructureType} import net.psforever.objects.serverobject.terminals.capture.{CaptureTerminal, CaptureTerminalAware, CaptureTerminalAwareBehavior} import net.psforever.services.galaxy.{GalaxyAction, GalaxyServiceMessage} import net.psforever.services.local.{LocalAction, LocalServiceMessage} @@ -84,7 +84,16 @@ case object CavernFacilityLogic ): Behavior[Command] = { BuildingActor.setFactionTo(details, faction, log) val building = details.building - building.Neighbours.getOrElse(Nil).foreach { _.Actor ! BuildingActor.AlertToFactionChange(building) } + val gates: Iterable[Building] = building.Zone.Buildings.values.filter(_.BuildingType == StructureType.WarpGate) + gates.foreach { g => + val neighbors = g.Neighbours.getOrElse(Nil) + neighbors.collect { + case otherWg: Building => otherWg + } + .filter(_.Zone != g.Zone) + .foreach { otherGate => otherGate.Zone.actor ! ZoneActor.ZoneMapUpdate() + } + } Behaviors.same } diff --git a/src/main/scala/net/psforever/actors/zone/building/MajorFacilityLogic.scala b/src/main/scala/net/psforever/actors/zone/building/MajorFacilityLogic.scala index 47415ec3a..4dd21dcb7 100644 --- a/src/main/scala/net/psforever/actors/zone/building/MajorFacilityLogic.scala +++ b/src/main/scala/net/psforever/actors/zone/building/MajorFacilityLogic.scala @@ -10,6 +10,7 @@ import net.psforever.objects.serverobject.generator.{Generator, GeneratorControl import net.psforever.objects.serverobject.structures.{Amenity, Building} import net.psforever.objects.serverobject.terminals.capture.{CaptureTerminal, CaptureTerminalAware, CaptureTerminalAwareBehavior} import net.psforever.objects.sourcing.PlayerSource +import net.psforever.packet.game.PlanetsideAttributeMessage import net.psforever.services.{InterstellarClusterService, Service} import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.galaxy.{GalaxyAction, GalaxyServiceMessage} @@ -244,6 +245,8 @@ case object MajorFacilityLogic } setFactionTo(details, PlanetSideEmpire.NEUTRAL) details.asInstanceOf[MajorFacilityWrapper].hasNtuSupply = false + details.building.Zone.lockedBy = PlanetSideEmpire.NEUTRAL + details.building.Zone.NotifyContinentalLockBenefits(details.building.Zone, details.building) Behaviors.same } @@ -300,6 +303,12 @@ case object MajorFacilityLogic building.PlayersInSOI.foreach { player => events ! AvatarServiceMessage(player.Name, msg) } + if (building.hasCavernLockBenefit) { + zone.LocalEvents ! LocalServiceMessage( + zone.id, + LocalAction.SendResponse(PlanetsideAttributeMessage(building.GUID, 67, 0)) + ) + } false case Some(GeneratorControl.Event.Destroyed) => true diff --git a/src/main/scala/net/psforever/actors/zone/building/WarpGateLogic.scala b/src/main/scala/net/psforever/actors/zone/building/WarpGateLogic.scala index 194ce913c..b3eb01c1f 100644 --- a/src/main/scala/net/psforever/actors/zone/building/WarpGateLogic.scala +++ b/src/main/scala/net/psforever/actors/zone/building/WarpGateLogic.scala @@ -122,9 +122,6 @@ case object WarpGateLogic } updateBroadcastCapabilitiesOfWarpGate(details, wg, setBroadcastTo) updateBroadcastCapabilitiesOfWarpGate(details, otherWg, setBroadcastTo) - if (wg.Zone.map.cavern && !otherWg.Zone.map.cavern) { - otherWg.Zone.actor ! ZoneActor.ZoneMapUpdate() - } case (Some(_), Some(wg : WarpGate), Some(otherWg : WarpGate), None) => handleWarpGateDeadendPair(details, otherWg, wg) diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 39731ad4e..c5b198fb1 100644 --- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -1125,6 +1125,8 @@ object GlobalDefinitions { val medical_terminal = new MedicalTerminalDefinition(529) + val medical_terminal_healing_module = new MedicalTerminalDefinition(530) + val portable_med_terminal = new MedicalTerminalDefinition(689) val pad_landing_frame = new MedicalTerminalDefinition(618) diff --git a/src/main/scala/net/psforever/objects/equipment/EffectTarget.scala b/src/main/scala/net/psforever/objects/equipment/EffectTarget.scala index 53aecdb94..3a2ce3941 100644 --- a/src/main/scala/net/psforever/objects/equipment/EffectTarget.scala +++ b/src/main/scala/net/psforever/objects/equipment/EffectTarget.scala @@ -26,13 +26,23 @@ object EffectTarget { //noinspection ScalaUnusedSymbol def Invalid(target: PlanetSideGameObject): Boolean = false - def Medical(target: PlanetSideGameObject): Boolean = + def Medical(target: PlanetSideGameObject): Boolean = { target match { case p: Player => p.Health > 0 && (p.Health < p.MaxHealth || p.Armor < p.MaxArmor) case _ => false } + } + + def HealthModule(target: PlanetSideGameObject): Boolean = { + target match { + case p: Player => + p.Health > 0 && p.Health < 120 + case _ => + false + } + } def HealthCrystal(target: PlanetSideGameObject): Boolean = target match { diff --git a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala index 551d62a40..ff136ba94 100644 --- a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala +++ b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala @@ -370,6 +370,15 @@ object GlobalDefinitionsMiscellaneous { medical_terminal.RepairIfDestroyed = true medical_terminal.Geometry = GeometryForm.representByCylinder(radius = 0.711f, height = 1.75f) + medical_terminal_healing_module.Name = "medical_terminal_healing_module" + medical_terminal_healing_module.Interval = 2000 + medical_terminal_healing_module.HealAmount = 1 + medical_terminal_healing_module.ArmorAmount = 0 + medical_terminal_healing_module.UseRadius = 300 + medical_terminal_healing_module.TargetValidation += EffectTarget.Category.Player -> EffectTarget.Validation.HealthModule + medical_terminal_healing_module.Damageable = false + medical_terminal_healing_module.Repairable = false + adv_med_terminal.Name = "adv_med_terminal" adv_med_terminal.Interval = 500 adv_med_terminal.HealAmount = 8 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 f0c06e71d..a7863ab56 100644 --- a/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala +++ b/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala @@ -36,6 +36,7 @@ class Building( private var participationFunc: ParticipationLogic = NoParticipation var virusId: Long = 8 // 8 default = no virus var virusInstalledBy: Option[Int] = None // faction id + var hasCavernLockBenefit: Boolean = false super.Zone_=(zone) super.GUID_=(PlanetSideGUID(building_guid)) //set Invalidate() //unset; guid can be used during setup, but does not stop being registered properly later @@ -206,11 +207,14 @@ class Building( } val cavernBenefit: Set[CavernBenefit] = if ( generatorState != PlanetSideGeneratorState.Destroyed && - faction != PlanetSideEmpire.NEUTRAL && - connectedCavern().nonEmpty + faction != PlanetSideEmpire.NEUTRAL && !CaptureTerminalIsHacked && + connectedCavern().exists(_.Zone.lockedBy == faction) ) { - Set(CavernBenefit.VehicleModule, CavernBenefit.EquipmentModule) + hasCavernLockBenefit = true + Set(CavernBenefit.VehicleModule, CavernBenefit.EquipmentModule, CavernBenefit.ShieldModule, + CavernBenefit.SpeedModule, CavernBenefit.HealthModule, CavernBenefit.PainModule) } else { + hasCavernLockBenefit = false Set(CavernBenefit.None) } val (installedVirus, installedByFac) = if (virusId == 8) { diff --git a/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala b/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala index 4e81bac5c..b26f1d617 100644 --- a/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala @@ -247,6 +247,9 @@ object ProximityTerminalControl { target: PlanetSideGameObject ): Boolean = { (terminal.Definition, target) match { + case (_: MedicalTerminalDefinition, p: Player) + if terminal.Definition == + GlobalDefinitions.medical_terminal_healing_module => HealthModule(terminal, p) case (_: MedicalTerminalDefinition, p: Player) => HealthAndArmorTerminal(terminal, p) case (_: WeaponRechargeTerminalDefinition, p: Player) => WeaponRechargeTerminal(terminal, p) case (_: MedicalTerminalDefinition, v: Vehicle) => VehicleRepairTerminal(terminal, v) @@ -269,6 +272,16 @@ object ProximityTerminalControl { fullHeal && fullRepair } + /** + * Activated by a facility having a linked cavern lock or health module installed. Friendly players + * within the SOI receive constant healing as requested by the client + */ + def HealthModule(unit: Terminal with ProximityUnit, target: Player): Boolean = { + val medDef = unit.Definition.asInstanceOf[MedicalTerminalDefinition] + val fullHeal = HealthModuleAction(unit, target, medDef.HealAmount, PlayerHealthCallback) + fullHeal + } + /** * When driving a vehicle close to a rearm/repair silo, * restore the vehicle's health points. @@ -318,6 +331,35 @@ object ProximityTerminalControl { } } + /** + * Heals players and increases their health/max health up to 120 if they enter the SOI this benefit is active in. + */ + def HealthModuleAction( + terminal: Terminal, + target: PlanetSideGameObject with Vitality with ZoneAware, + healAmount: Int, + updateFunc: PlanetSideGameObject with Vitality with ZoneAware => Unit + ): Boolean = { + val maxHealthCap = 120 + val zone = target.Zone + val oldMax = target.MaxHealth + val newMax = math.min(oldMax + healAmount, maxHealthCap) + + if (oldMax < maxHealthCap) { + target.MaxHealth = newMax + zone.AvatarEvents ! AvatarServiceMessage( + zone.id, + AvatarAction.PlanetsideAttributeToAll(target.GUID, 1, newMax) + ) + } + if (target.Health < newMax) { + target.Health = math.min(target.Health + healAmount, newMax) + target.LogActivity(HealFromTerminal(AmenitySource(terminal), 1)) + updateFunc(target) + } + target.Health == newMax + } + def PlayerHealthCallback(target: PlanetSideGameObject with Vitality with ZoneAware): Unit = { val zone = target.Zone zone.AvatarEvents ! AvatarServiceMessage( diff --git a/src/main/scala/net/psforever/objects/vital/etc/PainboxReason.scala b/src/main/scala/net/psforever/objects/vital/etc/PainboxReason.scala index 764697bd8..78753916c 100644 --- a/src/main/scala/net/psforever/objects/vital/etc/PainboxReason.scala +++ b/src/main/scala/net/psforever/objects/vital/etc/PainboxReason.scala @@ -1,7 +1,9 @@ // Copyright (c) 2020 PSForever package net.psforever.objects.vital.etc +import net.psforever.objects.Vehicle import net.psforever.objects.serverobject.painbox.Painbox +import net.psforever.objects.serverobject.structures.Building import net.psforever.objects.sourcing.SourceEntry import net.psforever.objects.vital.{NoResistanceSelection, SimpleResolutions} import net.psforever.objects.vital.base.{DamageReason, DamageResolution} @@ -13,7 +15,18 @@ final case class PainboxReason(entity: Painbox) extends DamageReason { private val definition = entity.Definition assert(definition.innateDamage.nonEmpty, s"causal entity '${definition.Name}' does not emit pain field") - def source: DamageWithPosition = definition.innateDamage.get + def source: DamageWithPosition = { + val base = definition.innateDamage.get + entity.Owner match { + case b: Building if b.hasCavernLockBenefit => + new DamageWithPosition { + Damage0 = 5 + DamageRadius = 0 + DamageToHealthOnly = true + } + case _ => base + } + } def resolution: DamageResolution.Value = DamageResolution.Resolved diff --git a/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala b/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala index a37b70389..6170985f2 100644 --- a/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala +++ b/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala @@ -259,7 +259,10 @@ class HackCaptureActor extends Actor { ) } // Push map update to clients - owner.Zone.actor ! ZoneActor.ZoneMapUpdate() + if (owner.BuildingType == StructureType.Tower) + owner.Actor ! BuildingActor.MapUpdate() + else + owner.Zone.actor ! ZoneActor.ZoneMapUpdate() } private def HackCompleted(terminal: CaptureTerminal with Hackable, hackedByFaction: PlanetSideEmpire.Value): Unit = { @@ -268,7 +271,6 @@ class HackCaptureActor extends Actor { building.virusId = 8 building.virusInstalledBy = None log.info(s"Setting base ${building.GUID} / MapId: ${building.MapId} as owned by $hackedByFaction") - building.Actor ! BuildingActor.SetFaction(hackedByFaction) //dispatch to players aligned with the capturing faction within the SOI val events = building.Zone.LocalEvents val msg = LocalAction.SendGenericActionMessage(Service.defaultPlayerGUID, GenericAction.FacilityCaptureFanfare) @@ -304,6 +306,7 @@ class HackCaptureActor extends Actor { building.Zone.lockedBy = PlanetSideEmpire.NEUTRAL building.Zone.NotifyContinentalLockBenefits(building.Zone, building) } + building.Actor ! BuildingActor.SetFaction(hackedByFaction) } else { log.info("Base hack completed, but base was out of NTU.") } @@ -333,23 +336,10 @@ class HackCaptureActor extends Actor { if (buildingIterator.hasNext) { val building = buildingIterator.next() val terminal = building.CaptureTerminal.get - val zone = building.Zone - val zoneActor = zone.actor val buildingActor = building.Actor - //clear any previous hack - if (building.CaptureTerminalIsHacked) { - zone.LocalEvents ! LocalServiceMessage( - zone.id, - LocalAction.ResecureCaptureTerminal(terminal, PlayerSource.Nobody) - ) - } - //push any updates this might cause - zoneActor ! ZoneActor.ZoneMapUpdate() - //convert faction affiliation buildingActor ! BuildingActor.SetFaction(faction) buildingActor ! BuildingActor.AmenityStateChange(terminal, Some(false)) - //push for map updates again - zoneActor ! ZoneActor.ZoneMapUpdate() + buildingActor ! BuildingActor.MapUpdate() } }, 0, diff --git a/src/main/scala/net/psforever/zones/Zones.scala b/src/main/scala/net/psforever/zones/Zones.scala index 44228780e..4ac2da6a6 100644 --- a/src/main/scala/net/psforever/zones/Zones.scala +++ b/src/main/scala/net/psforever/zones/Zones.scala @@ -345,6 +345,15 @@ object Zones { ), owningBuildingGuid = buildingGuid ) + //health module slowly heals friendly players in the soi + zoneMap.addLocalObject( + buildingGuid + 2, + ProximityTerminal.Constructor( + structure.position, + GlobalDefinitions.medical_terminal_healing_module + ), + owningBuildingGuid = buildingGuid + ) } } val filteredZoneEntities = @@ -557,7 +566,7 @@ object Zones { case "adv_med_terminal" | "repair_silo" | "pad_landing_frame" | "pad_landing_tower_frame" | "medical_terminal" | "crystals_health_a" | "crystals_health_b" | "crystals_repair_a" | "crystals_repair_b" | "crystals_vehicle_a" | - "crystals_vehicle_b" | "crystals_energy_a" | "crystals_energy_b" => + "crystals_vehicle_b" | "crystals_energy_a" | "crystals_energy_b" | "medical_terminal_healing_module" => zoneMap.addLocalObject( obj.guid, ProximityTerminal