cavern lock benefits, faster capturebase all checkpoint

This commit is contained in:
ScrawnyRonnie 2025-12-30 21:46:53 -05:00
parent 5b9e0ec384
commit 3f1efefc20
16 changed files with 248 additions and 52 deletions

View file

@ -45,6 +45,13 @@ class GalaxyHandlerLogic(val ops: SessionGalaxyHandlers, implicit val context: A
case GalaxyResponse.MapUpdate(msg) => case GalaxyResponse.MapUpdate(msg) =>
sendResponse(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) => case GalaxyResponse.UpdateBroadcastPrivileges(zoneId, gateMapId, fromFactions, toFactions) =>
val faction = player.Faction val faction = player.Faction

View file

@ -368,7 +368,8 @@ class ChatOperations(
case (Some(buildings), Some(faction), Some(_)) => case (Some(buildings), Some(faction), Some(_)) =>
//TODO implement timer //TODO implement timer
//schedule processing of buildings with a delay //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) zone.actor ! ZoneActor.AssignLockedBy(zone, notifyPlayers=true)
} }
true true
@ -382,6 +383,7 @@ class ChatOperations(
faction: PlanetSideEmpire.Value, faction: PlanetSideEmpire.Value,
delayMillis: Long delayMillis: Long
)(onComplete: Zone => Unit): Unit = { )(onComplete: Zone => Unit): Unit = {
import net.psforever.objects.serverobject.structures.StructureType
val buildingsToProcess = buildings.filter(b => b.CaptureTerminal.isDefined && b.Faction != faction) val buildingsToProcess = buildings.filter(b => b.CaptureTerminal.isDefined && b.Faction != faction)
val iterator = buildingsToProcess.iterator val iterator = buildingsToProcess.iterator
val zone = buildings.head.Zone val zone = buildings.head.Zone
@ -391,18 +393,23 @@ class ChatOperations(
if (iterator.hasNext) { if (iterator.hasNext) {
val building = iterator.next() val building = iterator.next()
val terminal = building.CaptureTerminal.get val terminal = building.CaptureTerminal.get
val zoneActor = zone.actor if (building.BuildingType == StructureType.Tower) {
if (building.CaptureTerminalIsHacked) { building.Actor ! BuildingActor.SetFaction(faction)
zone.LocalEvents ! LocalServiceMessage( building.Actor ! BuildingActor.AmenityStateChange(terminal, Some(false))
zone.id, building.Actor ! BuildingActor.MapUpdate()
LocalAction.ResecureCaptureTerminal(terminal, PlayerSource.Nobody)
)
} }
zoneActor ! ZoneActor.ZoneMapUpdate() else {
building.Actor ! BuildingActor.SetFaction(faction) if (building.CaptureTerminalIsHacked) {
building.Actor ! BuildingActor.AmenityStateChange(terminal, Some(false)) zone.LocalEvents ! LocalServiceMessage(
zoneActor ! ZoneActor.ZoneMapUpdate() zone.id,
} else { LocalAction.ResecureCaptureTerminal(terminal, PlayerSource.Nobody)
)
}
building.Actor ! BuildingActor.SetFaction(faction)
building.Actor ! BuildingActor.AmenityStateChange(terminal, Some(false))
}
}
else {
handle.cancel(false) handle.cancel(false)
onComplete(zone) onComplete(zone)
} }

View file

@ -342,6 +342,13 @@ class ZoningOperations(
sendResponse(PlanetsideAttributeMessage(targetPlayer.GUID, 19, 1)) 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 //load corpses in zone
continent.Corpses.foreach { continent.Corpses.foreach {
spawn.DepictPlayerAsCorpse spawn.DepictPlayerAsCorpse
@ -2943,16 +2950,40 @@ class ZoningOperations(
0 seconds 0 seconds
} else { } else {
//for other zones ... //for other zones ...
//Searhus lock benefit also gives biolab faster respawn val spawnTimeBenefit: Float = toSpawnPoint.Owner match {
val searhusBenefit = Zones.zones.find(_.Number == 9).exists(_.benefitRecipient == player.Faction) case b: Building => FasterRespawnBenefits(b)
//biolabs have/grant benefits case _ => 1f
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
} }
//TODO cumulative death penalty //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 buildingType == StructureType.Bunker
} }
.foreach { case (_, building) => .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() statisticsPacketFunc()
if (tplayer.ExoSuit == ExoSuitType.MAX) { 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) doorsThatShouldBeOpenInRange(pos, range = 100f)
setAvatar = true setAvatar = true
player.allowInteraction = true player.allowInteraction = true

View file

@ -167,7 +167,6 @@ object BuildingActor {
val building = details.building val building = details.building
val zone = building.Zone val zone = building.Zone
building.Faction = faction building.Faction = faction
zone.actor ! ZoneActor.ZoneMapUpdate() // Update entire lattice to show lattice benefits
zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.SetEmpire(building.GUID, faction)) zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.SetEmpire(building.GUID, faction))
} }
} }

View file

@ -8,7 +8,7 @@ import net.psforever.objects.serverobject.structures.{StructureType, WarpGate}
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.objects.zones.blockmap.{BlockMapEntity, SectorGroup} import net.psforever.objects.zones.blockmap.{BlockMapEntity, SectorGroup}
import net.psforever.objects.{ConstructionItem, PlanetSideGameObject, Player, Vehicle} 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 akka.actor.typed.scaladsl.adapter._
import net.psforever.actors.zone.building.MajorFacilityLogic import net.psforever.actors.zone.building.MajorFacilityLogic
import net.psforever.objects.avatar.scoring.Kill 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.sourcing.SourceEntry
import net.psforever.objects.vital.{InGameActivity, InGameHistory} import net.psforever.objects.vital.{InGameActivity, InGameHistory}
import net.psforever.objects.zones.exp.{ExperienceCalculator, SupportExperienceCalculator} import net.psforever.objects.zones.exp.{ExperienceCalculator, SupportExperienceCalculator}
import net.psforever.packet.game.{BuildingInfoUpdateMessage, PlanetsideAttributeMessage}
import net.psforever.util.Database._ import net.psforever.util.Database._
import net.psforever.persistence import net.psforever.persistence
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import scala.collection.mutable import scala.collection.mutable
import scala.util.{Failure, Success} 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 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 AssignLockedBy(zone: Zone, notifyPlayers: Boolean) extends Command
final case class BuildingInfoState(msg: BuildingInfoUpdateMessage) extends Command
} }
class ZoneActor( class ZoneActor(
@ -186,7 +190,7 @@ class ZoneActor(
case ZoneMapUpdate() => case ZoneMapUpdate() =>
zone.Buildings zone.Buildings
.filter(building => .filter(building =>
building._2.BuildingType == StructureType.Facility || building._2.BuildingType == StructureType.Tower) building._2.BuildingType == StructureType.Facility)
.values .values
.foreach(_.Actor ! BuildingActor.MapUpdate()) .foreach(_.Actor ! BuildingActor.MapUpdate())
Behaviors.same Behaviors.same
@ -194,6 +198,10 @@ class ZoneActor(
case AssignLockedBy(zone, notifyPlayers) => case AssignLockedBy(zone, notifyPlayers) =>
AssignLockedBy(zone, notifyPlayers) AssignLockedBy(zone, notifyPlayers)
Behaviors.same Behaviors.same
case BuildingInfoState(msg) =>
UpdateBuildingState(msg)
Behaviors.same
} }
.receiveSignal { .receiveSignal {
case (_, PostStop) => case (_, PostStop) =>
@ -221,4 +229,36 @@ class ZoneActor(
zone.benefitRecipient zone.benefitRecipient
if (facilities.nonEmpty && notifyPlayers) { zone.NotifyContinentalLockBenefits(zone, facilities.head) } 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 _ =>
}
}
}
} }

View file

@ -4,8 +4,8 @@ package net.psforever.actors.zone.building
import akka.actor.typed.Behavior import akka.actor.typed.Behavior
import akka.actor.typed.scaladsl.{ActorContext, Behaviors} import akka.actor.typed.scaladsl.{ActorContext, Behaviors}
import net.psforever.actors.commands.NtuCommand import net.psforever.actors.commands.NtuCommand
import net.psforever.actors.zone.{BuildingActor, BuildingControlDetails} import net.psforever.actors.zone.{BuildingActor, BuildingControlDetails, ZoneActor}
import net.psforever.objects.serverobject.structures.{Amenity, Building} import net.psforever.objects.serverobject.structures.{Amenity, Building, StructureType}
import net.psforever.objects.serverobject.terminals.capture.{CaptureTerminal, CaptureTerminalAware, CaptureTerminalAwareBehavior} import net.psforever.objects.serverobject.terminals.capture.{CaptureTerminal, CaptureTerminalAware, CaptureTerminalAwareBehavior}
import net.psforever.services.galaxy.{GalaxyAction, GalaxyServiceMessage} import net.psforever.services.galaxy.{GalaxyAction, GalaxyServiceMessage}
import net.psforever.services.local.{LocalAction, LocalServiceMessage} import net.psforever.services.local.{LocalAction, LocalServiceMessage}
@ -84,7 +84,16 @@ case object CavernFacilityLogic
): Behavior[Command] = { ): Behavior[Command] = {
BuildingActor.setFactionTo(details, faction, log) BuildingActor.setFactionTo(details, faction, log)
val building = details.building 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 Behaviors.same
} }

View file

@ -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.structures.{Amenity, Building}
import net.psforever.objects.serverobject.terminals.capture.{CaptureTerminal, CaptureTerminalAware, CaptureTerminalAwareBehavior} import net.psforever.objects.serverobject.terminals.capture.{CaptureTerminal, CaptureTerminalAware, CaptureTerminalAwareBehavior}
import net.psforever.objects.sourcing.PlayerSource import net.psforever.objects.sourcing.PlayerSource
import net.psforever.packet.game.PlanetsideAttributeMessage
import net.psforever.services.{InterstellarClusterService, Service} import net.psforever.services.{InterstellarClusterService, Service}
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.galaxy.{GalaxyAction, GalaxyServiceMessage} import net.psforever.services.galaxy.{GalaxyAction, GalaxyServiceMessage}
@ -244,6 +245,8 @@ case object MajorFacilityLogic
} }
setFactionTo(details, PlanetSideEmpire.NEUTRAL) setFactionTo(details, PlanetSideEmpire.NEUTRAL)
details.asInstanceOf[MajorFacilityWrapper].hasNtuSupply = false details.asInstanceOf[MajorFacilityWrapper].hasNtuSupply = false
details.building.Zone.lockedBy = PlanetSideEmpire.NEUTRAL
details.building.Zone.NotifyContinentalLockBenefits(details.building.Zone, details.building)
Behaviors.same Behaviors.same
} }
@ -300,6 +303,12 @@ case object MajorFacilityLogic
building.PlayersInSOI.foreach { player => building.PlayersInSOI.foreach { player =>
events ! AvatarServiceMessage(player.Name, msg) events ! AvatarServiceMessage(player.Name, msg)
} }
if (building.hasCavernLockBenefit) {
zone.LocalEvents ! LocalServiceMessage(
zone.id,
LocalAction.SendResponse(PlanetsideAttributeMessage(building.GUID, 67, 0))
)
}
false false
case Some(GeneratorControl.Event.Destroyed) => case Some(GeneratorControl.Event.Destroyed) =>
true true

View file

@ -122,9 +122,6 @@ case object WarpGateLogic
} }
updateBroadcastCapabilitiesOfWarpGate(details, wg, setBroadcastTo) updateBroadcastCapabilitiesOfWarpGate(details, wg, setBroadcastTo)
updateBroadcastCapabilitiesOfWarpGate(details, otherWg, 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) => case (Some(_), Some(wg : WarpGate), Some(otherWg : WarpGate), None) =>
handleWarpGateDeadendPair(details, otherWg, wg) handleWarpGateDeadendPair(details, otherWg, wg)

View file

@ -1125,6 +1125,8 @@ object GlobalDefinitions {
val medical_terminal = new MedicalTerminalDefinition(529) val medical_terminal = new MedicalTerminalDefinition(529)
val medical_terminal_healing_module = new MedicalTerminalDefinition(530)
val portable_med_terminal = new MedicalTerminalDefinition(689) val portable_med_terminal = new MedicalTerminalDefinition(689)
val pad_landing_frame = new MedicalTerminalDefinition(618) val pad_landing_frame = new MedicalTerminalDefinition(618)

View file

@ -26,13 +26,23 @@ object EffectTarget {
//noinspection ScalaUnusedSymbol //noinspection ScalaUnusedSymbol
def Invalid(target: PlanetSideGameObject): Boolean = false def Invalid(target: PlanetSideGameObject): Boolean = false
def Medical(target: PlanetSideGameObject): Boolean = def Medical(target: PlanetSideGameObject): Boolean = {
target match { target match {
case p: Player => case p: Player =>
p.Health > 0 && (p.Health < p.MaxHealth || p.Armor < p.MaxArmor) p.Health > 0 && (p.Health < p.MaxHealth || p.Armor < p.MaxArmor)
case _ => case _ =>
false false
} }
}
def HealthModule(target: PlanetSideGameObject): Boolean = {
target match {
case p: Player =>
p.Health > 0 && p.Health < 120
case _ =>
false
}
}
def HealthCrystal(target: PlanetSideGameObject): Boolean = def HealthCrystal(target: PlanetSideGameObject): Boolean =
target match { target match {

View file

@ -370,6 +370,15 @@ object GlobalDefinitionsMiscellaneous {
medical_terminal.RepairIfDestroyed = true medical_terminal.RepairIfDestroyed = true
medical_terminal.Geometry = GeometryForm.representByCylinder(radius = 0.711f, height = 1.75f) 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.Name = "adv_med_terminal"
adv_med_terminal.Interval = 500 adv_med_terminal.Interval = 500
adv_med_terminal.HealAmount = 8 adv_med_terminal.HealAmount = 8

View file

@ -36,6 +36,7 @@ class Building(
private var participationFunc: ParticipationLogic = NoParticipation private var participationFunc: ParticipationLogic = NoParticipation
var virusId: Long = 8 // 8 default = no virus var virusId: Long = 8 // 8 default = no virus
var virusInstalledBy: Option[Int] = None // faction id var virusInstalledBy: Option[Int] = None // faction id
var hasCavernLockBenefit: Boolean = false
super.Zone_=(zone) super.Zone_=(zone)
super.GUID_=(PlanetSideGUID(building_guid)) //set super.GUID_=(PlanetSideGUID(building_guid)) //set
Invalidate() //unset; guid can be used during setup, but does not stop being registered properly later 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 ( val cavernBenefit: Set[CavernBenefit] = if (
generatorState != PlanetSideGeneratorState.Destroyed && generatorState != PlanetSideGeneratorState.Destroyed &&
faction != PlanetSideEmpire.NEUTRAL && faction != PlanetSideEmpire.NEUTRAL && !CaptureTerminalIsHacked &&
connectedCavern().nonEmpty 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 { } else {
hasCavernLockBenefit = false
Set(CavernBenefit.None) Set(CavernBenefit.None)
} }
val (installedVirus, installedByFac) = if (virusId == 8) { val (installedVirus, installedByFac) = if (virusId == 8) {

View file

@ -247,6 +247,9 @@ object ProximityTerminalControl {
target: PlanetSideGameObject target: PlanetSideGameObject
): Boolean = { ): Boolean = {
(terminal.Definition, target) match { (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 (_: MedicalTerminalDefinition, p: Player) => HealthAndArmorTerminal(terminal, p)
case (_: WeaponRechargeTerminalDefinition, p: Player) => WeaponRechargeTerminal(terminal, p) case (_: WeaponRechargeTerminalDefinition, p: Player) => WeaponRechargeTerminal(terminal, p)
case (_: MedicalTerminalDefinition, v: Vehicle) => VehicleRepairTerminal(terminal, v) case (_: MedicalTerminalDefinition, v: Vehicle) => VehicleRepairTerminal(terminal, v)
@ -269,6 +272,16 @@ object ProximityTerminalControl {
fullHeal && fullRepair 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, * When driving a vehicle close to a rearm/repair silo,
* restore the vehicle's health points. * 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 = { def PlayerHealthCallback(target: PlanetSideGameObject with Vitality with ZoneAware): Unit = {
val zone = target.Zone val zone = target.Zone
zone.AvatarEvents ! AvatarServiceMessage( zone.AvatarEvents ! AvatarServiceMessage(

View file

@ -1,7 +1,9 @@
// Copyright (c) 2020 PSForever // Copyright (c) 2020 PSForever
package net.psforever.objects.vital.etc package net.psforever.objects.vital.etc
import net.psforever.objects.Vehicle
import net.psforever.objects.serverobject.painbox.Painbox import net.psforever.objects.serverobject.painbox.Painbox
import net.psforever.objects.serverobject.structures.Building
import net.psforever.objects.sourcing.SourceEntry import net.psforever.objects.sourcing.SourceEntry
import net.psforever.objects.vital.{NoResistanceSelection, SimpleResolutions} import net.psforever.objects.vital.{NoResistanceSelection, SimpleResolutions}
import net.psforever.objects.vital.base.{DamageReason, DamageResolution} 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 private val definition = entity.Definition
assert(definition.innateDamage.nonEmpty, s"causal entity '${definition.Name}' does not emit pain field") 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 def resolution: DamageResolution.Value = DamageResolution.Resolved

View file

@ -259,7 +259,10 @@ class HackCaptureActor extends Actor {
) )
} }
// Push map update to clients // 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 = { private def HackCompleted(terminal: CaptureTerminal with Hackable, hackedByFaction: PlanetSideEmpire.Value): Unit = {
@ -268,7 +271,6 @@ class HackCaptureActor extends Actor {
building.virusId = 8 building.virusId = 8
building.virusInstalledBy = None building.virusInstalledBy = None
log.info(s"Setting base ${building.GUID} / MapId: ${building.MapId} as owned by $hackedByFaction") log.info(s"Setting base ${building.GUID} / MapId: ${building.MapId} as owned by $hackedByFaction")
building.Actor ! BuildingActor.SetFaction(hackedByFaction)
//dispatch to players aligned with the capturing faction within the SOI //dispatch to players aligned with the capturing faction within the SOI
val events = building.Zone.LocalEvents val events = building.Zone.LocalEvents
val msg = LocalAction.SendGenericActionMessage(Service.defaultPlayerGUID, GenericAction.FacilityCaptureFanfare) val msg = LocalAction.SendGenericActionMessage(Service.defaultPlayerGUID, GenericAction.FacilityCaptureFanfare)
@ -304,6 +306,7 @@ class HackCaptureActor extends Actor {
building.Zone.lockedBy = PlanetSideEmpire.NEUTRAL building.Zone.lockedBy = PlanetSideEmpire.NEUTRAL
building.Zone.NotifyContinentalLockBenefits(building.Zone, building) building.Zone.NotifyContinentalLockBenefits(building.Zone, building)
} }
building.Actor ! BuildingActor.SetFaction(hackedByFaction)
} else { } else {
log.info("Base hack completed, but base was out of NTU.") log.info("Base hack completed, but base was out of NTU.")
} }
@ -333,23 +336,10 @@ class HackCaptureActor extends Actor {
if (buildingIterator.hasNext) { if (buildingIterator.hasNext) {
val building = buildingIterator.next() val building = buildingIterator.next()
val terminal = building.CaptureTerminal.get val terminal = building.CaptureTerminal.get
val zone = building.Zone
val zoneActor = zone.actor
val buildingActor = building.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.SetFaction(faction)
buildingActor ! BuildingActor.AmenityStateChange(terminal, Some(false)) buildingActor ! BuildingActor.AmenityStateChange(terminal, Some(false))
//push for map updates again buildingActor ! BuildingActor.MapUpdate()
zoneActor ! ZoneActor.ZoneMapUpdate()
} }
}, },
0, 0,

View file

@ -345,6 +345,15 @@ object Zones {
), ),
owningBuildingGuid = buildingGuid 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 = val filteredZoneEntities =
@ -557,7 +566,7 @@ object Zones {
case "adv_med_terminal" | "repair_silo" | "pad_landing_frame" | "pad_landing_tower_frame" | "medical_terminal" | 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_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( zoneMap.addLocalObject(
obj.guid, obj.guid,
ProximityTerminal ProximityTerminal