From 1d57cca1d3587949abc413b2036ef8697d462af8 Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Sun, 7 Dec 2025 23:39:43 -0500 Subject: [PATCH] adding entity to represent force dome, and wiring force dome to capitol facility; touching the force dome while it is active causes death on both server and client --- .../actors/session/csr/GeneralLogic.scala | 24 ++++++- .../actors/session/normal/GeneralLogic.scala | 20 +++++- .../psforever/objects/GlobalDefinitions.scala | 13 ++++ .../dome/ForceDomeDefinition.scala | 9 +++ .../serverobject/dome/ForceDomePhysics.scala | 40 ++++++++++++ .../objects/vital/etc/ForceDomeExposure.scala | 62 +++++++++++++++++++ .../objects/vital/etc/SuicideReason.scala | 2 +- .../scala/net/psforever/zones/Zones.scala | 37 +++++++---- 8 files changed, 190 insertions(+), 17 deletions(-) create mode 100644 src/main/scala/net/psforever/objects/serverobject/dome/ForceDomeDefinition.scala create mode 100644 src/main/scala/net/psforever/objects/serverobject/dome/ForceDomePhysics.scala create mode 100644 src/main/scala/net/psforever/objects/vital/etc/ForceDomeExposure.scala 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 f2b59f251..76e8f2761 100644 --- a/src/main/scala/net/psforever/actors/session/csr/GeneralLogic.scala +++ b/src/main/scala/net/psforever/actors/session/csr/GeneralLogic.scala @@ -14,6 +14,7 @@ import net.psforever.objects.equipment.Equipment import net.psforever.objects.inventory.Container import net.psforever.objects.serverobject.{CommonMessages, ServerObject} import net.psforever.objects.serverobject.containable.Containable +import net.psforever.objects.serverobject.dome.ForceDomePhysics import net.psforever.objects.serverobject.doors.Door import net.psforever.objects.serverobject.generator.Generator import net.psforever.objects.serverobject.llu.CaptureFlag @@ -26,11 +27,14 @@ import net.psforever.objects.serverobject.terminals.{ProximityUnit, Terminal} import net.psforever.objects.serverobject.terminals.implant.ImplantTerminalMech import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects.serverobject.turret.FacilityTurret +import net.psforever.objects.sourcing.{PlayerSource, SourceEntry} import net.psforever.objects.vehicles.Utility import net.psforever.objects.vital.Vitality +import net.psforever.objects.vital.etc.ForceDomeExposure +import net.psforever.objects.vital.interaction.DamageInteraction import net.psforever.objects.zones.{ZoneProjectile, Zoning} import net.psforever.packet.PlanetSideGamePacket -import net.psforever.packet.game.OutfitEventAction.{OutfitInfo, OutfitRankNames, Initial, Unk1} +import net.psforever.packet.game.OutfitEventAction.{Initial, OutfitInfo, OutfitRankNames, Unk1} 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, OutfitEvent, OutfitMemberEvent, OutfitMembershipRequest, OutfitMembershipResponse, OutfitRequest, OutfitRequestAction, PickupItemMessage, PlanetsideAttributeMessage, PlayerStateMessageUpstream, RequestDestroyMessage, TargetingImplantRequest, TerrainCondition, TradeMessage, UnuseItemMessage, UseItemMessage, VoiceHostInfo, VoiceHostRequest, ZipLineMessage} import net.psforever.services.RemoverActor import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} @@ -538,7 +542,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex def handleGenericCollision(pkt: GenericCollisionMsg): Unit = { player.BailProtection = false - val GenericCollisionMsg(ctype, p, _, _, pv, _, _, _, _, _, _, _) = pkt + val GenericCollisionMsg(ctype, p, _, _, pv, t, _, _, _, _, _, _) = pkt if (pv.z * pv.z >= (pv.x * pv.x + pv.y * pv.y) * 0.5f) { if (ops.heightTrend) { ops.heightHistory = ops.heightLast @@ -555,8 +559,22 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex v.BailProtection = false case (CollisionIs.OfAircraft, Some(v: Vehicle)) if v.Definition.CanFly && v.Seats(0).occupant.contains(player) => () + case (CollisionIs.BetweenThings, Some(field: ForceDomePhysics)) /*if field.Energized*/ => + val target = sessionLogic + .vehicles + .findLocalVehicle + .getOrElse(player) + target.Actor ! Vitality.Damage( + DamageInteraction( + PlayerSource(player), + ForceDomeExposure(SourceEntry(field)), + player.Position + ).calculate() + ) + target.BailProtection = false + player.BailProtection = false case (CollisionIs.BetweenThings, _) => - log.warn("GenericCollision: CollisionIs.BetweenThings detected - no handling case") + log.warn(s"GenericCollision: CollisionIs.BetweenThings detected - no handling case for obj id:${t.guid}") case _ => () } } 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 449980c49..1db6ea648 100644 --- a/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala @@ -16,6 +16,7 @@ import net.psforever.objects.inventory.Container import net.psforever.objects.serverobject.{PlanetSideServerObject, ServerObject} import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.containable.Containable +import net.psforever.objects.serverobject.dome.ForceDomePhysics import net.psforever.objects.serverobject.doors.Door import net.psforever.objects.serverobject.generator.Generator import net.psforever.objects.serverobject.interior.Sidedness.OutsideOf @@ -29,11 +30,11 @@ import net.psforever.objects.serverobject.terminals.{ProximityUnit, Terminal} import net.psforever.objects.serverobject.terminals.implant.ImplantTerminalMech import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects.serverobject.turret.FacilityTurret -import net.psforever.objects.sourcing.SourceEntry +import net.psforever.objects.sourcing.{PlayerSource, SourceEntry} import net.psforever.objects.vehicles.Utility import net.psforever.objects.vital.Vitality import net.psforever.objects.vital.collision.{CollisionReason, CollisionWithReason} -import net.psforever.objects.vital.etc.SuicideReason +import net.psforever.objects.vital.etc.{ForceDomeExposure, SuicideReason} import net.psforever.objects.vital.interaction.DamageInteraction import net.psforever.objects.zones.{ZoneProjectile, Zoning} import net.psforever.packet.PlanetSideGamePacket @@ -636,6 +637,21 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex case (CollisionIs.OfAircraft, out @ Some(v: Vehicle)) if v.Definition.CanFly && v.Seats(0).occupant.contains(player) => (out, sessionLogic.validObject(t, decorator = "GenericCollision/Aircraft"), false, pv) + case (CollisionIs.BetweenThings, Some(field: ForceDomePhysics)) /*if field.Energized*/ => + val target = sessionLogic + .vehicles + .findLocalVehicle + .getOrElse(player) + target.Actor ! Vitality.Damage( + DamageInteraction( + PlayerSource(player), + ForceDomeExposure(SourceEntry(field)), + player.Position + ).calculate() + ) + target.BailProtection = false + player.BailProtection = false + (None, None, false, Vector3.Zero) case (CollisionIs.BetweenThings, _) => log.warn("GenericCollision: CollisionIs.BetweenThings detected - no handling case") (None, None, false, Vector3.Zero) diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index c5b198fb1..573220471 100644 --- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -8,6 +8,7 @@ import net.psforever.objects.definition.converter._ import net.psforever.objects.equipment._ import net.psforever.objects.global.{GlobalDefinitionsAmmo, GlobalDefinitionsBuilding, GlobalDefinitionsDeployable, GlobalDefinitionsExoSuit, GlobalDefinitionsImplant, GlobalDefinitionsKit, GlobalDefinitionsMiscellaneous, GlobalDefinitionsProjectile, GlobalDefinitionsTool, GlobalDefinitionsVehicle} import net.psforever.objects.locker.LockerContainerDefinition +import net.psforever.objects.serverobject.dome.ForceDomeDefinition import net.psforever.objects.serverobject.doors.DoorDefinition import net.psforever.objects.serverobject.generator.GeneratorDefinition import net.psforever.objects.serverobject.locks.IFFLockDefinition @@ -1286,6 +1287,18 @@ object GlobalDefinitions { val zipline = new GenericTeleportationDefinition(1047) + val force_dome_generator = new ForceDomeDefinition(322) + + val force_dome_amp_physics = new ForceDomeDefinition(313) + + val force_dome_comm_physics = new ForceDomeDefinition(316) + + val force_dome_cryo_physics = new ForceDomeDefinition(319) + + val force_dome_dsp_physics = new ForceDomeDefinition(321) + + val force_dome_tech_physics = new ForceDomeDefinition(323) + /* Buildings */ diff --git a/src/main/scala/net/psforever/objects/serverobject/dome/ForceDomeDefinition.scala b/src/main/scala/net/psforever/objects/serverobject/dome/ForceDomeDefinition.scala new file mode 100644 index 000000000..b362682e6 --- /dev/null +++ b/src/main/scala/net/psforever/objects/serverobject/dome/ForceDomeDefinition.scala @@ -0,0 +1,9 @@ +// Copyright (c) 2025 PSForever +package net.psforever.objects.serverobject.dome + +import net.psforever.objects.serverobject.structures.AmenityDefinition + +class ForceDomeDefinition(objectId: Int) + extends AmenityDefinition(objectId) { + Name = "force_dome" +} diff --git a/src/main/scala/net/psforever/objects/serverobject/dome/ForceDomePhysics.scala b/src/main/scala/net/psforever/objects/serverobject/dome/ForceDomePhysics.scala new file mode 100644 index 000000000..3a4c0dc7b --- /dev/null +++ b/src/main/scala/net/psforever/objects/serverobject/dome/ForceDomePhysics.scala @@ -0,0 +1,40 @@ +// Copyright (c) 2025 PSForever +package net.psforever.objects.serverobject.dome + +import net.psforever.objects.serverobject.structures.Amenity +import net.psforever.types.Vector3 + +class ForceDomePhysics(private val cfddef: ForceDomeDefinition) + extends Amenity { + private var energized: Boolean = false + + def Energized: Boolean = energized + + def Energized_=(state: Boolean): Boolean = { + energized = state + Energized + } + + def Definition: ForceDomeDefinition = cfddef +} + +object ForceDomePhysics { + import akka.actor.ActorContext + + /** + * Instantiate and configure a `CapitolForceDome` object. + * @param pos positon of the object in the zone's coordinate system + * @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)(id: Int, context: ActorContext): ForceDomePhysics = { + //import akka.actor.Props + import net.psforever.objects.GlobalDefinitions + + val obj = new ForceDomePhysics(GlobalDefinitions.force_dome_generator) + obj.Position = pos + //obj.Actor = context.actorOf(Props(classOf[null], obj), s"${GlobalDefinitions.door.Name}_$id") + obj + } +} diff --git a/src/main/scala/net/psforever/objects/vital/etc/ForceDomeExposure.scala b/src/main/scala/net/psforever/objects/vital/etc/ForceDomeExposure.scala new file mode 100644 index 000000000..d09ddd880 --- /dev/null +++ b/src/main/scala/net/psforever/objects/vital/etc/ForceDomeExposure.scala @@ -0,0 +1,62 @@ +// Copyright (c) 2025 PSForever +package net.psforever.objects.vital.etc + +import net.psforever.objects.sourcing.{AmenitySource, SourceEntry} +import net.psforever.objects.vital.{NoResistanceSelection, SimpleResolutions} +import net.psforever.objects.vital.base.{DamageReason, DamageResolution} +import net.psforever.objects.vital.damage.DamageCalculations +import net.psforever.objects.vital.prop.DamageProperties +import net.psforever.objects.vital.resolution.{DamageAndResistance, DamageResistanceModel} + +/** + * A wrapper for a "damage source" in damage calculations that indicates a harmful interaction from a capitol force dome. + * @param field the target of the field in question + */ +final case class ForceDomeExposure(field: SourceEntry) + extends DamageReason { + def resolution: DamageResolution.Value = DamageResolution.Collision + + def same(test: DamageReason): Boolean = test match { + case eer: ForceDomeExposure => eer.field eq field + case _ => false + } + + /** + * Want to 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. + */ + override def adversary: Option[SourceEntry] = None +} + +object ForceDomeExposure { + final val drm = new DamageResistanceModel { + DamageUsing = DamageCalculations.AgainstExoSuit + ResistUsing = NoResistanceSelection + Model = SimpleResolutions.calculate + } + + final val damageProperties = new DamageProperties { + Damage0 = 99999 + DamageToHealthOnly = true + DamageToVehicleOnly = true + DamageToBattleframeOnly = true + } +} + diff --git a/src/main/scala/net/psforever/objects/vital/etc/SuicideReason.scala b/src/main/scala/net/psforever/objects/vital/etc/SuicideReason.scala index cf7f1f085..c80ce9c64 100644 --- a/src/main/scala/net/psforever/objects/vital/etc/SuicideReason.scala +++ b/src/main/scala/net/psforever/objects/vital/etc/SuicideReason.scala @@ -29,7 +29,7 @@ final case class SuicideReason() eventually, they stop logging in. Anyway, this has nothing to do with that. - Most playes probably just want to jump to the next base over. + Most players probably just want to jump to the next base over. */ def source: DamageProperties = SuicideReason.damageProperties diff --git a/src/main/scala/net/psforever/zones/Zones.scala b/src/main/scala/net/psforever/zones/Zones.scala index 4ac2da6a6..702924f74 100644 --- a/src/main/scala/net/psforever/zones/Zones.scala +++ b/src/main/scala/net/psforever/zones/Zones.scala @@ -11,6 +11,7 @@ import io.circe.parser._ import net.psforever.objects.{GlobalDefinitions, LocalLockerItem, LocalProjectile} import net.psforever.objects.definition.BasicDefinition import net.psforever.objects.guid.selector.{NumberSelector, RandomSelector, SpecificSelector} +import net.psforever.objects.serverobject.dome.ForceDomePhysics import net.psforever.objects.serverobject.doors.{Door, DoorDefinition, SpawnTubeDoor} import net.psforever.objects.serverobject.generator.Generator import net.psforever.objects.serverobject.llu.{CaptureFlagSocket, CaptureFlagSocketDefinition} @@ -100,17 +101,9 @@ object Zones { "PathPoints" )(ZipLinePath.apply) - // monolith, hst, warpgate are ignored for now as the scala code isn't ready to handle them. // BFR terminals/doors are ignored as top level elements as sanctuaries have them with no associated building. (repair_silo also has this problem, but currently is ignored in the AmenityExtrator project) // Force domes have GUIDs but are currently classed as separate entities. The dome is controlled by sending GOAM 44 / 48 / 52 to the building GUID - private val ignoredEntities = Seq( - "monolith", - "force_dome_dsp_physics", - "force_dome_comm_physics", - "force_dome_cryo_physics", - "force_dome_tech_physics", - "force_dome_amp_physics" - ) + private val ignoredEntities = Seq("monolith") private val towerTypes = Seq("tower_a", "tower_b", "tower_c") private val facilityTypes = Seq("amp_station", "cryo_facility", "comm_station", "comm_station_dsp", "tech_plant") @@ -127,6 +120,13 @@ object Zones { "vt_spawn", "vt_vehicle" ) + private val forceDomeTypes = Seq( + "force_dome_dsp_physics", + "force_dome_comm_physics", + "force_dome_cryo_physics", + "force_dome_tech_physics", + "force_dome_amp_physics" + ) private val cavernBuildingTypes = Seq( "ceiling_bldg_a", "ceiling_bldg_b", @@ -380,11 +380,27 @@ object Zones { createObjects( zoneMap, - zoneObjects.filterNot { _.objectType.startsWith("bfr_") }, + zoneObjects.filterNot { obj => obj.objectType.startsWith("bfr_") || forceDomeTypes.contains(obj.objectType) }, ownerGuid = 0, None, turretWeaponGuid ) + //force dome physics object are not owned + //for our benefit, we can attach them as amenities to the zone's capitol facility + zoneObjects + .find { obj => forceDomeTypes.contains(obj.objectType) } + .foreach { forceDome => + structures + .find { structure => Building.Capitols.contains(structure.objectName) } + .foreach { capitol => + zoneMap + .addLocalObject( + forceDome.guid, + ForceDomePhysics.Constructor(forceDome.position), + owningBuildingGuid = capitol.guid + ) + } + } lattice.asObject.get(mapid).foreach { obj => obj.asArray.get.foreach { entry => @@ -710,7 +726,6 @@ object Zones { case _ => () } - } }