From b5d60a7f9e70f45e7c5db34d703beb2a9bc0e769 Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Mon, 11 Mar 2024 18:14:22 -0400 Subject: [PATCH] integrating inside/outside considerations into server-calculated damage; clarifying the sidedness comparison rules; extending inside outside considerations for deployables and vehicles --- .../actors/session/support/SessionData.scala | 1 + .../session/support/ZoningOperations.scala | 41 ++++++++--- .../scala/net/psforever/objects/Player.scala | 2 +- .../scala/net/psforever/objects/Vehicle.scala | 14 +++- .../avatar/interaction/WithEntrance.scala | 56 +++++++++------ .../net/psforever/objects/ce/Deployable.scala | 4 +- .../objects/geometry/GeometryForm.scala | 16 +++++ .../VolumetricEnvironmentCollision.scala | 2 +- .../GlobalDefinitionsMiscellaneous.scala | 16 +++-- .../serverobject/doors/DoorDefinition.scala | 14 ++-- .../environment/EnvironmentAttribute.scala | 69 +++++++++++-------- .../serverobject/interior/InteriorAware.scala | 16 ++++- .../serverobject/interior/Sidedness.scala | 56 ++++++++++++--- .../vehicles/control/VehicleControl.scala | 3 +- .../interaction/WithEntranceInVehicle.scala | 50 ++++++++++++++ .../net/psforever/objects/zones/Zone.scala | 38 +++++++--- .../packet/game/InvalidTerrainMessage.scala | 10 +-- .../net/psforever/types/ChatMessageType.scala | 2 +- 18 files changed, 310 insertions(+), 100 deletions(-) create mode 100644 src/main/scala/net/psforever/objects/vehicles/interaction/WithEntranceInVehicle.scala diff --git a/src/main/scala/net/psforever/actors/session/support/SessionData.scala b/src/main/scala/net/psforever/actors/session/support/SessionData.scala index 9eba63b82..60eed356a 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionData.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionData.scala @@ -641,6 +641,7 @@ class SessionData( val dObj: Deployable = Deployables.Make(ammoType)() dObj.Position = pos dObj.Orientation = orient + dObj.WhichSide = player.WhichSide dObj.Faction = player.Faction dObj.AssignOwnership(player) val tasking: TaskBundle = dObj match { 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 f61853fe6..54b00181b 100644 --- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala @@ -10,6 +10,7 @@ import net.psforever.login.WorldSession import net.psforever.objects.avatar.BattleRank import net.psforever.objects.avatar.scoring.{CampaignStatistics, ScoreCard, SessionStatistics} import net.psforever.objects.inventory.InventoryItem +import net.psforever.objects.serverobject.interior.Sidedness import net.psforever.objects.serverobject.mount.Seat import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects.serverobject.turret.auto.AutomatedTurret @@ -2630,15 +2631,19 @@ class ZoningOperations( } else { Zones.zones.find { _.id.equals(zoneId) }.orElse(Some(Zone.Nowhere)).get.Number } + val toSide = physSpawnPoint.map(_.Owner) match { + case Some(_: WarpGate) => Sidedness.OutsideOf + case Some(_: Building) => Sidedness.InsideOf + case Some(v: Vehicle) => v.WhichSide //though usually OutsideOf + case _ => Sidedness.StrictlyBetweenSides //todo needs better determination + } val toSpawnPoint = physSpawnPoint.collect { case o: PlanetSideGameObject with FactionAffinity => SourceEntry(o) } respawnTimer = context.system.scheduler.scheduleOnce(respawnTime) { if (player.isBackpack) { // if the player is dead, he is handled as dead infantry, even if he died in a vehicle // new player is spawning val newPlayer = RespawnClone(player) - newPlayer.Position = pos - newPlayer.Orientation = ori newPlayer.LogActivity(SpawningActivity(PlayerSource(newPlayer), toZoneNumber, toSpawnPoint)) - LoadZoneAsPlayer(newPlayer, zoneId) + LoadZoneAsPlayUsing(newPlayer, pos, ori, toSide, zoneId) } else { avatarActor ! AvatarActor.DeactivateActiveImplants() val betterSpawnPoint = physSpawnPoint.collect { case o: PlanetSideGameObject with FactionAffinity with InGameHistory => o } @@ -2646,6 +2651,7 @@ class ZoningOperations( case Some(vehicle: Vehicle) => // driver or passenger in vehicle using a warp gate, or a droppod InGameHistory.SpawnReconstructionActivity(vehicle, toZoneNumber, betterSpawnPoint) InGameHistory.SpawnReconstructionActivity(player, toZoneNumber, betterSpawnPoint) + vehicle.WhichSide = toSide LoadZoneInVehicle(vehicle, pos, ori, zoneId) case _ if player.HasGUID => // player is deconstructing self or instant action @@ -2655,21 +2661,38 @@ class ZoningOperations( continent.id, AvatarAction.ObjectDelete(player_guid, player_guid, 4) ) - player.Position = pos - player.Orientation = ori InGameHistory.SpawnReconstructionActivity(player, toZoneNumber, betterSpawnPoint) - LoadZoneAsPlayer(player, zoneId) + LoadZoneAsPlayUsing(player, pos, ori, toSide, zoneId) case _ => //player is logging in - player.Position = pos - player.Orientation = ori InGameHistory.SpawnReconstructionActivity(player, toZoneNumber, betterSpawnPoint) - LoadZoneAsPlayer(player, zoneId) + LoadZoneAsPlayUsing(player, pos, ori, toSide, zoneId) } } } } + /** + * na + * @param target player being spawned + * @param position where player is being placed in the game wqrld + * @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 + */ + private def LoadZoneAsPlayUsing( + target: Player, + position: Vector3, + orientation: Vector3, + onThisSide: Sidedness, + goingToZone: String + ): Unit = { + target.Position = position + target.Orientation = orientation + target.WhichSide = onThisSide + LoadZoneAsPlayer(target, goingToZone) + } + /** * The user is either already in the current zone and merely transporting from one location to another, * also called "dying", or occasionally "deconstructing," diff --git a/src/main/scala/net/psforever/objects/Player.scala b/src/main/scala/net/psforever/objects/Player.scala index abe4d0d7c..94f5136a5 100644 --- a/src/main/scala/net/psforever/objects/Player.scala +++ b/src/main/scala/net/psforever/objects/Player.scala @@ -40,7 +40,7 @@ class Player(var avatar: Avatar) with AuraContainer with MountableEntity { interaction(environment.interaction.InteractWithEnvironment(Seq( - new WithEntrance(avatar.name), + new WithEntrance(), new WithWater(avatar.name), new WithLava(), new WithDeath(), diff --git a/src/main/scala/net/psforever/objects/Vehicle.scala b/src/main/scala/net/psforever/objects/Vehicle.scala index 5bd310564..2c067a698 100644 --- a/src/main/scala/net/psforever/objects/Vehicle.scala +++ b/src/main/scala/net/psforever/objects/Vehicle.scala @@ -1,6 +1,7 @@ // Copyright (c) 2017 PSForever package net.psforever.objects +import net.psforever.objects.avatar.interaction.WithEntrance import net.psforever.objects.ce.{InteractWithMines, InteractWithTurrets} import net.psforever.objects.definition.{ToolDefinition, VehicleDefinition} import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit} @@ -12,9 +13,10 @@ import net.psforever.objects.serverobject.aura.AuraContainer import net.psforever.objects.serverobject.deploy.Deployment import net.psforever.objects.serverobject.environment.interaction.common.{WithDeath, WithMovementTrigger} import net.psforever.objects.serverobject.hackable.Hackable +import net.psforever.objects.serverobject.interior.{InteriorAwareFromInteraction, Sidedness} import net.psforever.objects.serverobject.structures.AmenityOwner import net.psforever.objects.vehicles._ -import net.psforever.objects.vehicles.interaction.{WithLava, WithWater} +import net.psforever.objects.vehicles.interaction.{WithEntranceInVehicle, WithLava, WithWater} import net.psforever.objects.vital.resistance.StandardResistanceProfile import net.psforever.objects.vital.Vitality import net.psforever.objects.vital.resolution.DamageResistanceModel @@ -90,8 +92,10 @@ class Vehicle(private val vehicleDef: VehicleDefinition) with CommonNtuContainer with Container with AuraContainer - with MountableEntity { + with MountableEntity + with InteriorAwareFromInteraction { interaction(environment.interaction.InteractWithEnvironment(Seq( + new WithEntrance(), new WithWater(), new WithLava(), new WithDeath(), @@ -513,6 +517,12 @@ class Vehicle(private val vehicleDef: VehicleDefinition) !Definition.CanFly && super.BailProtection_=(protect) } + override def WhichSide_=(thisSide: Sidedness): Sidedness = { + val toSide = super.WhichSide_=(thisSide) + (Seats.values ++ CargoHolds.values).flatMap(_.occupants).foreach(_.WhichSide = toSide) + toSide + } + /** * This is the definition entry that is used to store and unload pertinent information about the `Vehicle`. * @return the vehicle's definition entry diff --git a/src/main/scala/net/psforever/objects/avatar/interaction/WithEntrance.scala b/src/main/scala/net/psforever/objects/avatar/interaction/WithEntrance.scala index 84daad7cc..6385d140c 100644 --- a/src/main/scala/net/psforever/objects/avatar/interaction/WithEntrance.scala +++ b/src/main/scala/net/psforever/objects/avatar/interaction/WithEntrance.scala @@ -4,18 +4,18 @@ package net.psforever.objects.avatar.interaction import net.psforever.objects.serverobject.doors.{Door, InteriorDoorPassage} import net.psforever.objects.serverobject.environment.{EnvironmentAttribute, EnvironmentTrait, PieceOfEnvironment, interaction} import net.psforever.objects.serverobject.environment.interaction.{InteractionWith, RespondsToZoneEnvironment} -import net.psforever.objects.serverobject.interior.Sidedness +import net.psforever.objects.serverobject.interior.{Sidedness, TraditionalInteriorAware} import net.psforever.objects.zones.InteractsWithZone import net.psforever.types.Vector3 import scala.concurrent.duration._ -class WithEntrance(val channel: String) - extends InteractionWith { +class WithEntrance() + extends InteractionWith + with TraditionalInteriorAware { val attribute: EnvironmentTrait = EnvironmentAttribute.InteriorField private var stopTest: Boolean = false - private var sideAware: Sidedness = Sidedness.InBetweenSides def doInteractingWith( obj: InteractsWithZone, @@ -26,12 +26,14 @@ class WithEntrance(val channel: String) stopTest = false } else { val door = body.asInstanceOf[InteriorDoorPassage].door - if (door.isOpen) { - sideAware = Sidedness.InBetweenSides + val strictly = performInteriorCheck(obj, door) + val value = if (door.isOpen) { + Sidedness.InBetweenSides(door, strictly) } else { - performInteriorCheck(obj, door) + strictly } - obj.Actor ! RespondsToZoneEnvironment.Timer(attribute, delay = 250 milliseconds, obj.Actor, interaction.InteractingWithEnvironment(body, Some("bellybutton"))) + WhichSide_=(value) + obj.Actor ! RespondsToZoneEnvironment.Timer(attribute, delay = 250.milliseconds, obj.Actor, interaction.InteractingWithEnvironment(body, Some("bellybutton"))) } } @@ -40,7 +42,7 @@ class WithEntrance(val channel: String) body: PieceOfEnvironment, data: Option[Any] ): Unit = { - performInteriorCheck(obj, body.asInstanceOf[InteriorDoorPassage].door) + WhichSide_=(performInteriorCheck(obj, body.asInstanceOf[InteriorDoorPassage].door)) stopTest = true } @@ -48,32 +50,44 @@ class WithEntrance(val channel: String) obj: InteractsWithZone, door: Door ): Sidedness = { + debugInteriorCheck(obj, door) +// if (Vector3.DotProduct(Vector3.Unit(obj.Position - door.Position), door.Outwards) > 0f) { +// Sidedness.OutsideOf +// } else { +// Sidedness.InsideOf +// } + } + + private def debugInteriorCheck( + obj: InteractsWithZone, + door: Door + ): Sidedness = { + import net.psforever.objects.{Player, Vehicle} import net.psforever.packet.game.ChatMsg import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.types.{ChatMessageType, PlanetSideGUID} + val channel = obj match { + case p: Player => p.Name + case v: Vehicle => v.Actor.toString() + case _ => "" + } val result = Vector3.DotProduct(Vector3.Unit(obj.Position - door.Position), door.Outwards) > 0f - if (result && sideAware != Sidedness.OutsideOf) { + if (result && WhichSide != Sidedness.OutsideOf) { //outside - sideAware = Sidedness.OutsideOf obj.Zone.AvatarEvents ! AvatarServiceMessage( channel, AvatarAction.SendResponse(PlanetSideGUID(0), ChatMsg(ChatMessageType.UNK_229, "You are now outside")) ) - } else if (!result && sideAware != Sidedness.InsideOf) { + Sidedness.OutsideOf + } else if (!result && WhichSide != Sidedness.InsideOf) { //inside - sideAware = Sidedness.InsideOf obj.Zone.AvatarEvents ! AvatarServiceMessage( channel, AvatarAction.SendResponse(PlanetSideGUID(0), ChatMsg(ChatMessageType.UNK_229, "You are now inside")) ) + Sidedness.InsideOf + } else { + WhichSide } - sideAware - } - - def ThisSide: Sidedness = sideAware - - def ThisSide_=(thisSide: Sidedness): Unit = { - sideAware = thisSide - ThisSide } } diff --git a/src/main/scala/net/psforever/objects/ce/Deployable.scala b/src/main/scala/net/psforever/objects/ce/Deployable.scala index d9025f8c9..65387285b 100644 --- a/src/main/scala/net/psforever/objects/ce/Deployable.scala +++ b/src/main/scala/net/psforever/objects/ce/Deployable.scala @@ -5,6 +5,7 @@ import net.psforever.objects._ import net.psforever.objects.definition.DeployableDefinition import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.affinity.FactionAffinity +import net.psforever.objects.serverobject.interior.TraditionalInteriorAware import net.psforever.objects.vital.Vitality import net.psforever.objects.vital.resolution.DamageResistanceModel import net.psforever.objects.zones.ZoneAware @@ -18,7 +19,8 @@ trait BaseDeployable with BlockMapEntity with Vitality with OwnableByPlayer - with ZoneAware { + with ZoneAware + with TraditionalInteriorAware { private var faction: PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL private var shields: Int = 0 diff --git a/src/main/scala/net/psforever/objects/geometry/GeometryForm.scala b/src/main/scala/net/psforever/objects/geometry/GeometryForm.scala index 83a6e1ace..40a9afa54 100644 --- a/src/main/scala/net/psforever/objects/geometry/GeometryForm.scala +++ b/src/main/scala/net/psforever/objects/geometry/GeometryForm.scala @@ -112,6 +112,22 @@ object GeometryForm { } } + /** + * The geometric representation is a cylinder + * offset with respects to the height of the entity's base. + * @param radius half the distance across + * @param height how tall the cylinder is (the distance of the top to the base) + * @param o the entity + * @return the representation + */ + def representByRaisedCylinder(radius: Float, height: Float)(o: Any): VolumetricGeometry = { + o match { + case p: PlanetSideGameObject => Cylinder(p.Position + Vector3.z(height * 0.5f), Vector3.relativeUp(p.Orientation), radius, math.abs(height)) + case s: SourceEntry => Cylinder(s.Position + Vector3.z(height * 0.5f), Vector3.relativeUp(s.Orientation), radius, math.abs(height)) + case _ => invalidCylinder + } + } + /** * The geometric representation is a cylinder around the entity's base * if the target represents a player entity. diff --git a/src/main/scala/net/psforever/objects/geometry/VolumetricEnvironmentCollision.scala b/src/main/scala/net/psforever/objects/geometry/VolumetricEnvironmentCollision.scala index d09a5166d..e2ae08ec6 100644 --- a/src/main/scala/net/psforever/objects/geometry/VolumetricEnvironmentCollision.scala +++ b/src/main/scala/net/psforever/objects/geometry/VolumetricEnvironmentCollision.scala @@ -5,7 +5,7 @@ import net.psforever.objects.PlanetSideGameObject import net.psforever.objects.geometry.d2.Rectangle import net.psforever.objects.geometry.d3.VolumetricGeometry import net.psforever.objects.serverobject.doors.Door -import net.psforever.objects.serverobject.environment.{EnvironmentAttribute, EnvironmentCollision, EnvironmentTrait, PieceOfEnvironment} +import net.psforever.objects.serverobject.environment.EnvironmentCollision import net.psforever.objects.zones.Zone import net.psforever.types.Vector3 diff --git a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala index c01293ba6..bdd4b888f 100644 --- a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala +++ b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala @@ -648,25 +648,31 @@ object GlobalDefinitionsMiscellaneous { gr_door_airlock.Name = "gr_door_airlock" gr_door_ext.Name = "gr_door_ext" - gr_door_ext.geometryInteractionRadius = Some(1) + gr_door_ext.geometryInteractionRadius = Some(1.9f) gr_door_garage_ext.Name = "gr_door_garage_ext" - gr_door_garage_ext.geometryInteractionRadius = Some(1) + gr_door_garage_ext.initialOpeningDistance = 8f + gr_door_garage_ext.continuousOpenDistance = 9f + gr_door_garage_ext.geometryInteractionRadius = Some(11) + gr_door_garage_ext.geometryInteractionHeight = Some(-11) + gr_door_garage_ext.geometryInteractionCenterOn = true gr_door_garage_int.Name = "gr_door_garage_int" + gr_door_garage_int.initialOpeningDistance = 8f + gr_door_garage_int.continuousOpenDistance = 9f gr_door_int.Name = "gr_door_int" gr_door_main.Name = "gr_door_main" - gr_door_main.geometryInteractionRadius = Some(1) + gr_door_main.geometryInteractionRadius = Some(2.75f) gr_door_mb_ext.Name = "gr_door_mb_ext" - gr_door_mb_ext.geometryInteractionRadius = Some(1) + gr_door_mb_ext.geometryInteractionRadius = Some(2) gr_door_mb_int.Name = "gr_door_mb_int" gr_door_mb_lrg.Name = "gr_door_mb_lrg" - gr_door_mb_lrg.geometryInteractionRadius = Some(1) + gr_door_mb_lrg.geometryInteractionRadius = Some(2.5f) gr_door_mb_obsd.Name = "gr_door_mb_obsd" diff --git a/src/main/scala/net/psforever/objects/serverobject/doors/DoorDefinition.scala b/src/main/scala/net/psforever/objects/serverobject/doors/DoorDefinition.scala index ca03cbcdf..f75d409dc 100644 --- a/src/main/scala/net/psforever/objects/serverobject/doors/DoorDefinition.scala +++ b/src/main/scala/net/psforever/objects/serverobject/doors/DoorDefinition.scala @@ -2,6 +2,7 @@ package net.psforever.objects.serverobject.doors import net.psforever.objects.geometry.GeometryForm +import net.psforever.objects.geometry.d3.VolumetricGeometry import net.psforever.objects.serverobject.structures.AmenityDefinition /** @@ -18,12 +19,15 @@ class DoorDefinition(objectId: Int) var geometryInteractionRadius: Option[Float] = None var geometryInteractionHeight: Option[Float] = None + var geometryInteractionCenterOn: Boolean = false - Geometry = { - (geometryInteractionRadius, geometryInteractionHeight) match { - case (Some(r), Some(h)) => GeometryForm.representByCylinder(r, h) - case (Some(r), None) => GeometryForm.representBySphereOnBase(r) - case _ => super.Geometry + override def Geometry: Any => VolumetricGeometry = { + (geometryInteractionRadius, geometryInteractionHeight, geometryInteractionCenterOn) match { + case (Some(r), Some(h), false) => GeometryForm.representByCylinder(r, h) + case (Some(r), Some(h), true) => GeometryForm.representByRaisedCylinder(r, h) + case (Some(r), None, false) => GeometryForm.representBySphereOnBase(r) + case (Some(r), None, true) => GeometryForm.representBySphere(r) + case _ => super.Geometry } } } diff --git a/src/main/scala/net/psforever/objects/serverobject/environment/EnvironmentAttribute.scala b/src/main/scala/net/psforever/objects/serverobject/environment/EnvironmentAttribute.scala index 9fb5c7e66..37641049e 100644 --- a/src/main/scala/net/psforever/objects/serverobject/environment/EnvironmentAttribute.scala +++ b/src/main/scala/net/psforever/objects/serverobject/environment/EnvironmentAttribute.scala @@ -1,7 +1,6 @@ // Copyright (c) 2020-2024 PSForever package net.psforever.objects.serverobject.environment -import net.psforever.objects.serverobject.interior.InteriorAware import net.psforever.objects.{PlanetSideGameObject, Player, Vehicle} import net.psforever.objects.vital.Vitality import net.psforever.types.Vector3 @@ -18,32 +17,25 @@ object EnvironmentAttribute { /** water can only interact with objects that are negatively affected by being exposed to water; * it's better this way */ def canInteractWith(obj: PlanetSideGameObject): Boolean = { - obj.Definition.DrownAtMaxDepth || obj.Definition.DisableAtMaxDepth || (obj match { - case p: Player => p.VehicleSeated.isEmpty - case v: Vehicle => v.MountedIn.isEmpty - case _ => true - }) + obj.Definition.DrownAtMaxDepth || + obj.Definition.DisableAtMaxDepth || + canInteractWithPlayersAndVehicles(obj) || + (obj match { + case p: Player => p.VehicleSeated.isEmpty + case v: Vehicle => v.MountedIn.isEmpty + case _ => false + }) } } case object Lava extends EnvironmentTrait { /** lava can only interact with anything capable of registering damage */ - def canInteractWith(obj: PlanetSideGameObject): Boolean = { - obj match { - case o: Vitality => o.Definition.Damageable - case _ => false - } - } + def canInteractWith(obj: PlanetSideGameObject): Boolean = canInteractWithDamagingFields(obj) } case object Death extends EnvironmentTrait { /** death can only interact with anything capable of registering damage */ - def canInteractWith(obj: PlanetSideGameObject): Boolean = { - obj match { - case o: Vitality => o.Definition.Damageable - case _ => false - } - } + def canInteractWith(obj: PlanetSideGameObject): Boolean = canInteractWithDamagingFields(obj) } case object GantryDenialField @@ -60,22 +52,39 @@ object EnvironmentAttribute { case object MovementFieldTrigger extends EnvironmentTrait { /** only interact with living player characters or vehicles */ - def canInteractWith(obj: PlanetSideGameObject): Boolean = { - obj match { - case p: Player => p.isAlive && p.Position != Vector3.Zero - case v: Vehicle => !v.Destroyed && v.Position != Vector3.Zero - case _ => false - } - } + def canInteractWith(obj: PlanetSideGameObject): Boolean = canInteractWithPlayersAndVehicles(obj) } case object InteriorField extends EnvironmentTrait { - def canInteractWith(obj: PlanetSideGameObject): Boolean = { - obj match { - case _: InteriorAware => true - case _ => false - } + /** only interact with living player characters or vehicles */ + def canInteractWith(obj: PlanetSideGameObject): Boolean = canInteractWithPlayersAndVehicles(obj) + } + + /** + * This environment field only interacts with anything capable of registering damage. + * Also, exclude targets that are located at the game world origin. + * @param obj target entity + * @return whether or not this field affects the target entity + */ + def canInteractWithPlayersAndVehicles(obj: PlanetSideGameObject): Boolean = { + (obj.Position != Vector3.Zero) || + (obj match { + case p: Player => p.isAlive + case v: Vehicle => !v.Destroyed + case _ => false + }) + } + + /** + * This environment field only interacts with living player characters or not-destroyed vehicles. + * @param obj target entity + * @return whether or not this field affects the target entity + */ + def canInteractWithDamagingFields(obj: PlanetSideGameObject): Boolean = { + obj match { + case o: Vitality => !o.Destroyed && o.Definition.Damageable + case _ => false } } } diff --git a/src/main/scala/net/psforever/objects/serverobject/interior/InteriorAware.scala b/src/main/scala/net/psforever/objects/serverobject/interior/InteriorAware.scala index a687bd818..f29e85874 100644 --- a/src/main/scala/net/psforever/objects/serverobject/interior/InteriorAware.scala +++ b/src/main/scala/net/psforever/objects/serverobject/interior/InteriorAware.scala @@ -12,6 +12,18 @@ trait InteriorAware { def WhichSide_=(@unused thisSide: Sidedness): Sidedness } +trait TraditionalInteriorAware + extends InteriorAware { + private var side: Sidedness = Sidedness.StrictlyBetweenSides + + def WhichSide: Sidedness = side + + def WhichSide_=(thisSide: Sidedness): Sidedness = { + side = thisSide + WhichSide + } +} + trait InteriorAwareFromInteraction extends InteriorAware { awareness: InteractsWithZone => @@ -24,11 +36,11 @@ trait InteriorAwareFromInteraction } def WhichSide: Sidedness = { - withEntrance.map(_.ThisSide).getOrElse(Sidedness.InBetweenSides) + withEntrance.map(_.WhichSide).getOrElse(Sidedness.StrictlyBetweenSides) } def WhichSide_=(thisSide: Sidedness): Sidedness = { - withEntrance.foreach(_.ThisSide = thisSide) + withEntrance.foreach(_.WhichSide = thisSide) WhichSide } } diff --git a/src/main/scala/net/psforever/objects/serverobject/interior/Sidedness.scala b/src/main/scala/net/psforever/objects/serverobject/interior/Sidedness.scala index c7a36f2a6..bec498331 100644 --- a/src/main/scala/net/psforever/objects/serverobject/interior/Sidedness.scala +++ b/src/main/scala/net/psforever/objects/serverobject/interior/Sidedness.scala @@ -1,22 +1,60 @@ // Copyright (c) 2024 PSForever package net.psforever.objects.serverobject.interior -sealed trait Sidedness +import net.psforever.objects.serverobject.doors.Door -sealed trait Inside +sealed trait SidenessComparison -sealed trait Outside - -sealed trait InBetween extends Inside with Outside +sealed trait Sidedness { + protected def value: SidenessComparison +} object Sidedness { - case object InsideOf extends Inside with Sidedness + sealed trait Inside - case object OutsideOf extends Outside with Sidedness + sealed trait Outside - case object InBetweenSides extends InBetween with Sidedness + /* Comparison values */ + private case object IsInside extends SidenessComparison + + private case object IsOutside extends SidenessComparison + + private case object IsBetween extends SidenessComparison + + /* Immutable value containers */ + case object InsideOf extends Inside with Sidedness { + protected def value: SidenessComparison = IsInside + } + + case object OutsideOf extends Outside with Sidedness { + protected def value: SidenessComparison = IsOutside + } + + case object StrictlyBetweenSides extends Inside with Outside with Sidedness { + protected def value: SidenessComparison = IsBetween + } + + /* Mutable value container */ + class InBetweenSides( + private val door: Door, + private val strictly: Sidedness + ) extends Inside with Outside with Sidedness { + protected def value: SidenessComparison = { + if (door.isOpen) { + IsBetween + } else { + strictly.value + } + } + } + + object InBetweenSides { + def apply(door: Door, strictly: Sidedness): InBetweenSides = new InBetweenSides(door, strictly) + } def equals(a: Sidedness, b: Sidedness): Boolean = { - (a eq b) || a == Sidedness.InBetweenSides || b == Sidedness.InBetweenSides + val avalue = a.value + val bvalue = b.value + (avalue eq bvalue) || avalue == IsBetween || bvalue == IsBetween } } diff --git a/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala b/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala index 8cc6389b0..f4cec5a7d 100644 --- a/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala +++ b/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala @@ -319,11 +319,12 @@ class VehicleControl(vehicle: Vehicle) case Some(seatNumber) => val vsrc = VehicleSource(vehicle) user.LogActivity(VehicleMountActivity(vsrc, PlayerSource.inSeat(user, vsrc, seatNumber), vehicle.Zone.Number)) + obj.WhichSide = user.WhichSide //if the driver mount, change ownership if that is permissible for this vehicle if (seatNumber == 0 && !obj.OwnerName.contains(user.Name) && obj.Definition.CanBeOwned.nonEmpty) { //whatever vehicle was previously owned vehicle.Zone.GUID(user.avatar.vehicle) match { - case Some(v : Vehicle) => + case Some(v: Vehicle) => v.Actor ! Vehicle.Ownership(None) case _ => user.avatar.vehicle = None diff --git a/src/main/scala/net/psforever/objects/vehicles/interaction/WithEntranceInVehicle.scala b/src/main/scala/net/psforever/objects/vehicles/interaction/WithEntranceInVehicle.scala new file mode 100644 index 000000000..75bf9749e --- /dev/null +++ b/src/main/scala/net/psforever/objects/vehicles/interaction/WithEntranceInVehicle.scala @@ -0,0 +1,50 @@ +// Copyright (c) 2024 PSForever +package net.psforever.objects.vehicles.interaction + +import net.psforever.objects.Vehicle +import net.psforever.objects.avatar.interaction.WithEntrance +import net.psforever.objects.serverobject.doors.InteriorDoorPassage +import net.psforever.objects.serverobject.environment.PieceOfEnvironment +import net.psforever.objects.zones.InteractsWithZone + +class WithEntranceInVehicle + extends WithEntrance() { + private var warningLevel: Int = 0 + private var lastWarning: Long = 0L + + override def doInteractingWith(obj: InteractsWithZone, body: PieceOfEnvironment, data: Option[Any]): Unit = { + super.doInteractingWith(obj, body, data) + if (warningLevel == -1) { + warnAboutProximity(obj, msg = "@InvalidTerrain_VehicleNowSafe") + warningLevel = 0 + } else if (!body.asInstanceOf[InteriorDoorPassage].door.Definition.Name.contains("garage")) { + val curr = System.currentTimeMillis() + if (curr - lastWarning >= 5000L) { + if (warningLevel > 3) { + import scala.concurrent.duration._ + obj.Actor ! Vehicle.Deconstruct(Some(2.seconds)) + } else if (warningLevel > 0) { + warnAboutProximity(obj, msg = "@InvalidTerrain_VehicleWillDeconstruct") + } + lastWarning = curr + warningLevel += 1 + } + } + } + + override def stopInteractingWith(obj: InteractsWithZone, body: PieceOfEnvironment, data: Option[Any]): Unit = { + super.stopInteractingWith(obj, body, data) + warningLevel = -1 + } + + private def warnAboutProximity(obj: InteractsWithZone, msg: String): Unit = { + import net.psforever.packet.game.ChatMsg + import net.psforever.services.Service + import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} + import net.psforever.types.ChatMessageType + obj.Zone.AvatarEvents ! AvatarServiceMessage( + obj.Actor.toString(), + AvatarAction.SendResponse(Service.defaultPlayerGUID, ChatMsg(ChatMessageType.UNK_227, msg)) + ) + } +} diff --git a/src/main/scala/net/psforever/objects/zones/Zone.scala b/src/main/scala/net/psforever/objects/zones/Zone.scala index 71f2cf285..fbf0289f9 100644 --- a/src/main/scala/net/psforever/objects/zones/Zone.scala +++ b/src/main/scala/net/psforever/objects/zones/Zone.scala @@ -40,6 +40,7 @@ import net.psforever.objects.guid.pool.NumberPool import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.doors.Door +import net.psforever.objects.serverobject.interior.{InteriorAware, Sidedness} import net.psforever.objects.serverobject.locks.IFFLock import net.psforever.objects.serverobject.terminals.implant.ImplantTerminalMech import net.psforever.objects.serverobject.shuttle.OrbitalShuttlePad @@ -882,8 +883,6 @@ object Zone { private def AssignDoors(zone: Zone): Unit = { //let ZoneActor's sanity check catch any missing entities val map = zone.map - val guid = zone.guid - val invalidOutwards = Vector3(0,0,-1) //down if (map.cavern) { //cavern doors //todo what do? @@ -966,9 +965,6 @@ object Zone { volatileUnpairedDoors = volatileUnpairedDoors.slice(1, indexOfClosestDoor) ++ volatileUnpairedDoors.drop(indexOfClosestDoor + 1) pairedDoors = pairedDoors :+ (sampleDoor, otherDoor) } - volatileUnpairedDoors.foreach { door => - door.Outwards = invalidOutwards - } } pairedDoors.foreach { case (door1, door2) => //give each paired courtyard door an outward-ness @@ -1455,7 +1451,10 @@ object Zone { source: PlanetSideGameObject with Vitality, damagePropertiesBySource: DamageWithPosition ): List[PlanetSideServerObject with Vitality] = { - findAllTargets(zone, source.Position, damagePropertiesBySource).filter { target => target ne source } + allOnSameSide( + source, + findAllTargets(zone, source.Position, damagePropertiesBySource).filter { target => target ne source } + ) } /** @@ -1477,7 +1476,28 @@ object Zone { source: PlanetSideGameObject with Vitality, damagePropertiesBySource: DamageWithPosition ): List[PlanetSideServerObject with Vitality] = { - findAllTargets(zone, sourcePosition, damagePropertiesBySource).filter { target => target ne source } + allOnSameSide( + source, + findAllTargets(zone, sourcePosition, damagePropertiesBySource).filter { target => target ne source } + ) + } + + private def allOnSameSide( + source: PlanetSideGameObject with Vitality, + targets: List[PlanetSideServerObject with Vitality] + ): List[PlanetSideServerObject with Vitality] = { + source match { + case awareSource: InteriorAware => + val awareSide = awareSource.WhichSide + targets.flatMap { + case awareTarget: InteriorAware if !Sidedness.equals(awareSide, awareTarget.WhichSide) => + None + case anyTarget => + Some(anyTarget) + } + case _ => + targets + } } /** @@ -1524,7 +1544,9 @@ object Zone { target: PlanetSideGameObject with FactionAffinity with Vitality ): DamageInteraction = { explosionDamage(instigation, target.Position)(source, target) - }/** + } + + /** * na * @param instigation what previous event happened, if any, that caused this explosion * @param explosionPosition the coordinates of the detected explosion diff --git a/src/main/scala/net/psforever/packet/game/InvalidTerrainMessage.scala b/src/main/scala/net/psforever/packet/game/InvalidTerrainMessage.scala index 383836426..d90101496 100644 --- a/src/main/scala/net/psforever/packet/game/InvalidTerrainMessage.scala +++ b/src/main/scala/net/psforever/packet/game/InvalidTerrainMessage.scala @@ -1,10 +1,12 @@ // Copyright (c) 2021 PSForever package net.psforever.packet.game +import net.psforever.packet.GamePacketOpcode.Type import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} import net.psforever.types.{PlanetSideGUID, Vector3} import scodec.Attempt.Successful -import scodec.Codec +import scodec.bits.BitVector +import scodec.{Attempt, Codec} import scodec.codecs._ import shapeless.{::, HNil} @@ -15,7 +17,7 @@ object TerrainCondition extends Enumeration { type Type = Value val Safe, Unsafe = Value - implicit val codec = PacketHelpers.createEnumerationCodec(e = this, uint(bits = 1)) + implicit val codec: Codec[TerrainCondition.Value] = PacketHelpers.createEnumerationCodec(e = this, uint(bits = 1)) } /** @@ -34,8 +36,8 @@ final case class InvalidTerrainMessage( pos: Vector3 ) extends PlanetSideGamePacket { type Packet = InvalidTerrainMessage - def opcode = GamePacketOpcode.InvalidTerrainMessage - def encode = InvalidTerrainMessage.encode(this) + def opcode: Type = GamePacketOpcode.InvalidTerrainMessage + def encode: Attempt[BitVector] = InvalidTerrainMessage.encode(this) } object InvalidTerrainMessage extends Marshallable[InvalidTerrainMessage] { diff --git a/src/main/scala/net/psforever/types/ChatMessageType.scala b/src/main/scala/net/psforever/types/ChatMessageType.scala index dd0d9654c..589062be6 100644 --- a/src/main/scala/net/psforever/types/ChatMessageType.scala +++ b/src/main/scala/net/psforever/types/ChatMessageType.scala @@ -247,7 +247,7 @@ object ChatMessageType extends Enum[ChatMessageType] { case object CMT_ADD_VANUMODULE extends ChatMessageType // /moduleadd case object CMT_REMOVE_VANUMODULE extends ChatMessageType // /moduleremove OR /modulerm case object CMT_DEBUG_MASSIVE extends ChatMessageType // /debugmassive - case object CMT_WARP_TO_NEXT_BILLBOARD extends ChatMessageType // ??? + case object CMT_WARP_TO_NEXT_BILLBOARD extends ChatMessageType // The "Next" button on the /debugmassive UI produces this message. case object UNK_222 extends ChatMessageType // "CTF Flag stolen" // Plays a trumpet-like sound and displays the message: " has spawned a LLU" // "It must be taken to 's Control Console within 15 minutes or the hack will fail!"