diff --git a/src/main/scala/net/psforever/objects/Player.scala b/src/main/scala/net/psforever/objects/Player.scala index 4cb25871e..3681936fa 100644 --- a/src/main/scala/net/psforever/objects/Player.scala +++ b/src/main/scala/net/psforever/objects/Player.scala @@ -40,6 +40,7 @@ class Player(var avatar: Avatar) with InteriorAwareFromInteraction with AuraContainer with MountableEntity { + interaction(new InteractWithForceDomeProtection()) interaction(environment.interaction.InteractWithEnvironment(Seq( new WithEntrance(), new WithWater(avatar.name), @@ -51,7 +52,6 @@ class Player(var avatar: Avatar) interaction(new InteractWithMines(range = 10, TriggerOnPlayerRule)) interaction(new InteractWithTurrets()) interaction(new InteractWithRadiationClouds(range = 10f, Some(this))) - interaction(new InteractWithForceDomeProtection()) private var backpack: Boolean = false private var released: Boolean = false diff --git a/src/main/scala/net/psforever/objects/avatar/interaction/InteractWithForceDomeProtection.scala b/src/main/scala/net/psforever/objects/avatar/interaction/InteractWithForceDomeProtection.scala index fc4062791..145c8514e 100644 --- a/src/main/scala/net/psforever/objects/avatar/interaction/InteractWithForceDomeProtection.scala +++ b/src/main/scala/net/psforever/objects/avatar/interaction/InteractWithForceDomeProtection.scala @@ -24,7 +24,9 @@ class InteractWithForceDomeProtection private var protectedBy: Option[ForceDomePhysics] = None /** - * na + * If the target is protected, do conditions allow it to remain protected? + * If the target was vulnerable, can it be protected? + * Five second pause between evaluations (0-3, wait; 4, test). * @see `ForceDomeControl.TargetUnderForceDome` * @param sector the portion of the block map being tested * @param target the fixed element in this test @@ -70,19 +72,24 @@ class InteractWithForceDomeProtection ForceDomeControl.TargetUnderForceDome(dome.Perimeter)(target, dome, maxDistance = 0f) } .map { dome => - protectedBy = Some(dome) - target.Actor ! Damageable.MakeInvulnerable + applyProtection(target, dome) dome } } + protected def applyProtection(target: InteractsWithZone, dome: ForceDomePhysics): Unit = { + protectedBy = Some(dome) + target.Actor ! Damageable.MakeInvulnerable + } + /** - * na + * No longer invulnerable (if ever). + * Set the counter to force a reevaluation of the vulnerability state next turn. * @see `Damageable.MakeVulnerable` * @param target the fixed element in this test */ def resetInteraction(target: InteractsWithZone): Unit = { - protectSkipCounter = 0 + protectSkipCounter = 5 protectedBy = None target.Actor ! Damageable.MakeVulnerable } diff --git a/src/main/scala/net/psforever/objects/serverobject/dome/ForceDomeControl.scala b/src/main/scala/net/psforever/objects/serverobject/dome/ForceDomeControl.scala index 8b66a5a1c..28b665866 100644 --- a/src/main/scala/net/psforever/objects/serverobject/dome/ForceDomeControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/dome/ForceDomeControl.scala @@ -157,6 +157,8 @@ object ForceDomeControl { * and has the capitol force dome has a dependency upon it, * pass a message onto that facility that it should check its own state alignment. * @param building facility with `dome` + * @param dome force dome + * @return current state of the capitol force dome */ def AlignForceDomeStatusAndUpdate(building: Building, dome: ForceDomePhysics): Boolean = { val energizedState = dome.Energized @@ -182,6 +184,8 @@ object ForceDomeControl { * and has the capitol force dome has a dependency upon it, * pass a message onto that facility that it should check its own state alignment. * @param building facility with `dome` + * @param dome force dome + * @return current state of the capitol force dome */ private def AlignForceDomeStatus(building: Building, dome: ForceDomePhysics): Boolean = { val energizedState = dome.Energized @@ -202,7 +206,8 @@ object ForceDomeControl { * This is the usual fate of opponents upon it being expanded (energized). * @see `Zone.serverSideDamage` * @param dome force dome - * @return a list of affected entities + * @param perimeter ground-level perimeter of the force dome is defined by these segments (as vertex pairs) + * @return list of affected entities */ def ForceDomeKills(dome: ForceDomePhysics, perimeter: List[(Vector3, Vector3)]): List[PlanetSideServerObject] = { Zone.serverSideDamage( @@ -222,9 +227,9 @@ object ForceDomeControl { * @return a `DamageInteraction` object */ private def makesContactWithForceDome( - source: PlanetSideGameObject with FactionAffinity with Vitality, - target: PlanetSideGameObject with FactionAffinity with Vitality - ): DamageInteraction = { + source: PlanetSideGameObject with FactionAffinity with Vitality, + target: PlanetSideGameObject with FactionAffinity with Vitality + ): DamageInteraction = { DamageInteraction( SourceEntry(target), ForceDomeExposure(SourceEntry(source)), @@ -233,7 +238,11 @@ object ForceDomeControl { } /** - * na + * To be considered within a force dome, a target entity must satisfy two orientations + * where the second condition is one of two qualifications: + * 1. within an angular perimeter boundary, and + * 2a. below the base coordinate of the force dome or + * 2b. within a region above the base of the force dome represented by a literal "dome" (half of a sphere). * @see `Zone.distanceCheck` * @param segments ground-level perimeter of the force dome is defined by these segments (as vertex pairs) * @param obj1 a game entity, should be the force dome @@ -470,9 +479,6 @@ class ForceDomeControl(dome: ForceDomePhysics) //dome activating context.system.scheduler.scheduleOnce(delay = 1500 milliseconds, self, ForceDomeControl.Purge) context.system.scheduler.scheduleOnce(delay = 4000 milliseconds, self, ForceDomeControl.ApplyProtection) - } else if (oldState && !newState) { - //dome de-activating - dome.Zone.blockMap.removeFrom(dome) } newState case Some(state) => diff --git a/src/main/scala/net/psforever/objects/serverobject/dome/ForceDomeDefinition.scala b/src/main/scala/net/psforever/objects/serverobject/dome/ForceDomeDefinition.scala index e6df1ecdb..5f4d50450 100644 --- a/src/main/scala/net/psforever/objects/serverobject/dome/ForceDomeDefinition.scala +++ b/src/main/scala/net/psforever/objects/serverobject/dome/ForceDomeDefinition.scala @@ -26,19 +26,16 @@ class ForceDomeDefinition(objectId: Int) object ForceDomeDefinition { /** - * na - * @param o na - * @return na + * Transform a capitol force dome into a bounded geometric representation. + * @param o any entity from which to produce a geometric representation + * @return geometric representation */ def representBy(o: Any): VolumetricGeometry = { - import net.psforever.objects.geometry.GeometryForm.invalidPoint o match { case fdp: ForceDomePhysics => Sphere(fdp.Position, fdp.Definition.UseRadius) - case s: SourceEntry => - Sphere(s.Position, s.Definition.UseRadius) case _ => - Sphere(invalidPoint, 1f) + net.psforever.objects.geometry.GeometryForm.invalidPoint } } } diff --git a/src/main/scala/net/psforever/objects/serverobject/dome/ForceDomePhysics.scala b/src/main/scala/net/psforever/objects/serverobject/dome/ForceDomePhysics.scala index 2b2641537..1cd60c2a0 100644 --- a/src/main/scala/net/psforever/objects/serverobject/dome/ForceDomePhysics.scala +++ b/src/main/scala/net/psforever/objects/serverobject/dome/ForceDomePhysics.scala @@ -8,10 +8,20 @@ import net.psforever.types.Vector3 class ForceDomePhysics(private val cfddef: ForceDomeDefinition) extends Amenity with CaptureTerminalAware { + /** whether the dome is active or not */ private var energized: Boolean = false - + /** defined perimeter of this force dome on the floor; + * the walls created by this perimeter are angled inwards towards the facility, but that's not a consideration */ private var perimeter: List[(Vector3, Vector3)] = List() + override def Position: Vector3 = Owner.Position + + override def Position_=(vec: Vector3): Vector3 = Owner.Position + + override def Orientation: Vector3 = Owner.Orientation + + override def Orientation_=(vec: Vector3): Vector3 = Owner.Orientation + def Energized: Boolean = energized def Energized_=(state: Boolean): Boolean = { @@ -34,17 +44,15 @@ object ForceDomePhysics { /** * Instantiate and configure a `CapitolForceDome` object. - * @param pos position of the object in the zone's coordinate system * @param fddef specific type of force dome * @param id the unique id that will be assigned to this entity * @param context a context to allow the object to properly set up `ActorSystem` functionality * @return the `CapitolForceDome` object */ - def Constructor(pos: Vector3, fddef: ForceDomeDefinition)(id: Int, context: ActorContext): ForceDomePhysics = { + def Constructor(fddef: ForceDomeDefinition)(id: Int, context: ActorContext): ForceDomePhysics = { import akka.actor.Props val obj = new ForceDomePhysics(fddef) - obj.Position = pos obj.Actor = context.actorOf(Props(classOf[ForceDomeControl], obj), name = s"${fddef.Name}_$id") obj } diff --git a/src/main/scala/net/psforever/objects/serverobject/mount/InteractWithForceDomeProtectionSeatedInEntity.scala b/src/main/scala/net/psforever/objects/serverobject/mount/InteractWithForceDomeProtectionSeatedInEntity.scala new file mode 100644 index 000000000..fdbf6d081 --- /dev/null +++ b/src/main/scala/net/psforever/objects/serverobject/mount/InteractWithForceDomeProtectionSeatedInEntity.scala @@ -0,0 +1,36 @@ +// Copyright (c) 2025 PSForever +package net.psforever.objects.serverobject.mount + +import net.psforever.objects.avatar.interaction.InteractWithForceDomeProtection +import net.psforever.objects.serverobject.damage.Damageable +import net.psforever.objects.serverobject.dome.ForceDomePhysics +import net.psforever.objects.zones.InteractsWithZone + +class InteractWithForceDomeProtectionSeatedInEntity +extends InteractWithForceDomeProtection { + override def range: Float = 30f + + override protected def applyProtection(target: InteractsWithZone, dome: ForceDomePhysics): Unit = { + super.applyProtection(target, dome) + target + .asInstanceOf[Mountable] + .Seats + .values + .flatMap(_.occupants) + .foreach { occupant => + occupant.Actor ! Damageable.MakeInvulnerable + } + } + + override def resetInteraction(target: InteractsWithZone): Unit = { + super.resetInteraction(target) + target + .asInstanceOf[Mountable] + .Seats + .values + .flatMap(_.occupants) + .foreach { occupant => + occupant.Actor ! Damageable.MakeVulnerable + } + } +} diff --git a/src/main/scala/net/psforever/objects/vehicles/interaction/InteractWithForceDomeProtectionSeatedInVehicle.scala b/src/main/scala/net/psforever/objects/vehicles/interaction/InteractWithForceDomeProtectionSeatedInVehicle.scala new file mode 100644 index 000000000..181ca2cfe --- /dev/null +++ b/src/main/scala/net/psforever/objects/vehicles/interaction/InteractWithForceDomeProtectionSeatedInVehicle.scala @@ -0,0 +1,49 @@ +// Copyright (c) 2025 PSForever +package net.psforever.objects.vehicles.interaction + +import net.psforever.objects.Vehicle +import net.psforever.objects.avatar.interaction.{ForceZoneProtection, InteractWithForceDomeProtection} +import net.psforever.objects.serverobject.dome.ForceDomePhysics +import net.psforever.objects.serverobject.mount.InteractWithForceDomeProtectionSeatedInEntity +import net.psforever.objects.zones.InteractsWithZone + +class InteractWithForceDomeProtectionSeatedInVehicle + extends InteractWithForceDomeProtectionSeatedInEntity { + override protected def applyProtection(target: InteractsWithZone, dome: ForceDomePhysics): Unit = { + super.applyProtection(target, dome) + target + .asInstanceOf[Vehicle] + .CargoHolds + .values + .flatMap(_.occupants) + .foreach { vehicle => + vehicle + .interaction() + .find(_.Type == ForceZoneProtection) + .foreach { + case interaction: InteractWithForceDomeProtection => + interaction.applyProtection(vehicle, dome) + case _ => () + } + } + } + + override def resetInteraction(target: InteractsWithZone): Unit = { + super.resetInteraction(target) + target + .asInstanceOf[Vehicle] + .CargoHolds + .values + .flatMap(_.occupants) + .foreach { vehicle => + vehicle + .interaction() + .find(_.Type == ForceZoneProtection) + .foreach { + case interaction: InteractWithForceDomeProtection => + interaction.resetInteraction(vehicle) + case _ => () + } + } + } +} diff --git a/src/main/scala/net/psforever/objects/vital/etc/ForceDomeExposure.scala b/src/main/scala/net/psforever/objects/vital/etc/ForceDomeExposure.scala index 557a7e061..d2aa5ce4d 100644 --- a/src/main/scala/net/psforever/objects/vital/etc/ForceDomeExposure.scala +++ b/src/main/scala/net/psforever/objects/vital/etc/ForceDomeExposure.scala @@ -22,25 +22,19 @@ final case class ForceDomeExposure(field: SourceEntry) } /** - * Want to blame the capitol facility that is being protected. + * Blame the capitol facility that is being protected. */ override def attribution: Int = field match { case a: AmenitySource => a.installation.Definition.ObjectId case _ => field.Definition.ObjectId } - /** - * A direct connection to the damage information, numbers and properties. - */ override def source: DamageProperties = ForceDomeExposure.damageProperties - /** - * The functionality that is necessary for interaction of a vital game object with the rest of the hostile game world. - */ override def damageModel: DamageAndResistance = ForceDomeExposure.drm /** - * The person to be blamed for this. + * No one person will be blamed for this. */ override def adversary: Option[SourceEntry] = None } diff --git a/src/main/scala/net/psforever/zones/Zones.scala b/src/main/scala/net/psforever/zones/Zones.scala index 3682a9ad5..c94862623 100644 --- a/src/main/scala/net/psforever/zones/Zones.scala +++ b/src/main/scala/net/psforever/zones/Zones.scala @@ -392,12 +392,12 @@ object Zones { .foreach { forceDome => structures .find { structure => Building.Capitols.contains(structure.objectName) } - .foreach { capitol => + .foreach { structure => val definition = DefinitionUtil.fromString(forceDome.objectType).asInstanceOf[ForceDomeDefinition] zoneMap.addLocalObject( forceDome.guid, - ForceDomePhysics.Constructor(forceDome.position, definition), - owningBuildingGuid = capitol.guid + ForceDomePhysics.Constructor(definition), + owningBuildingGuid = structure.guid ) } }