integrating inside/outside considerations into server-calculated damage; clarifying the sidedness comparison rules; extending inside outside considerations for deployables and vehicles

This commit is contained in:
Fate-JH 2024-03-11 18:14:22 -04:00
parent b0d3c63b83
commit b5d60a7f9e
18 changed files with 310 additions and 100 deletions

View file

@ -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 {

View file

@ -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,"

View file

@ -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(),

View file

@ -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

View file

@ -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
}
}

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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"

View file

@ -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
}
}
}

View file

@ -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
}
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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

View file

@ -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))
)
}
}

View file

@ -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

View file

@ -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] {

View file

@ -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: "<Base Type> <Base Name> has spawned a LLU"
// "It must be taken to <Base Type> <Base Name>'s Control Console within 15 minutes or the hack will fail!"