Entity Interference (#1191)

* basic interference fields; setting up operations for ce deployables

* applying corrected properties to object definitions and applying them to interference tests for deployable ce and for deploying vehicles; made two otherwise useles object definitions to store property data (if we ever need ziplines or teleport pads ...)

* sent deployment requests to a centralized pipeline for interference testing; swapped out math.pow(a,2) for a * a

* temporary interference for vehicles that are going to transition to deployed eventually and block other deploying vehicles

* reversed the origin of the interference tests

* exception for non-interference resolves to wrong conclusion

* ramshackle merge conflict resolution; fixing interference for non-interference deploying vehicles
This commit is contained in:
Fate-JH 2024-05-11 00:33:07 -04:00 committed by GitHub
parent 426ab84f0a
commit a3eb3a8a95
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 383 additions and 103 deletions

View file

@ -533,7 +533,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
case _ =>
GUIDTask.registerObject(continent.GUID, dObj)
}
TaskWorkflow.execute(CallBackForTask(tasking, continent.Deployables, Zone.Deployable.BuildByOwner(dObj, player, obj)))
TaskWorkflow.execute(CallBackForTask(tasking, continent.Deployables, Zone.Deployable.BuildByOwner(dObj, player, obj), context.self))
case Some(obj) =>
log.warn(s"DeployObject: what is $obj, ${player.Name}? It's not a construction tool!")
case None =>

View file

@ -4,6 +4,7 @@ package net.psforever.actors.session.normal
import akka.actor.Actor.Receive
import akka.actor.ActorRef
import net.psforever.actors.session.support.{ChatFunctions, GeneralFunctions, LocalHandlerFunctions, MountHandlerFunctions, SquadHandlerFunctions, TerminalHandlerFunctions, VehicleFunctions, VehicleHandlerFunctions, WeaponAndProjectileFunctions}
import net.psforever.objects.Players
import net.psforever.packet.game.UplinkRequest
import net.psforever.services.chat.ChatService
//
@ -248,9 +249,11 @@ class NormalModeLogic(data: SessionData) extends ModeLogic {
case _: Zone.Vehicle.HasDespawned => ;
case Zone.Deployable.IsDismissed(obj: TurretDeployable) => //only if target deployable was never fully introduced
Players.buildCooldownReset(data.continent, data.player.Name, obj)
TaskWorkflow.execute(GUIDTask.unregisterDeployableTurret(data.continent.GUID, obj))
case Zone.Deployable.IsDismissed(obj) => //only if target deployable was never fully introduced
Players.buildCooldownReset(data.continent, data.player.Name, obj)
TaskWorkflow.execute(GUIDTask.unregisterObject(data.continent.GUID, obj))
case msg: Containable.ItemPutInSlot =>

View file

@ -272,24 +272,29 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
def handleDeployRequest(pkt: DeployRequestMessage): Unit = {
val DeployRequestMessage(_, vehicle_guid, deploy_state, _, _, _) = pkt
val vehicle = player.avatar.vehicle
if (vehicle.contains(vehicle_guid)) {
if (vehicle == player.VehicleSeated) {
continent.GUID(vehicle_guid) match {
case Some(obj: Vehicle) =>
continent.GUID(vehicle_guid)
.collect {
case obj: Vehicle =>
val vehicle = player.avatar.vehicle
if (!vehicle.contains(vehicle_guid)) {
log.warn(s"DeployRequest: ${player.Name} does not own the would-be-deploying ${obj.Definition.Name}")
} else if (vehicle != player.VehicleSeated) {
log.warn(s"${player.Name} must be mounted as the driver to request a deployment change")
} else {
log.info(s"${player.Name} is requesting a deployment change for ${obj.Definition.Name} - $deploy_state")
obj.Actor ! Deployment.TryDeploymentChange(deploy_state)
case _ =>
log.error(s"DeployRequest: ${player.Name} can not find vehicle $vehicle_guid")
avatarActor ! AvatarActor.SetVehicle(None)
}
} else {
log.warn(s"${player.Name} must be mounted to request a deployment change")
continent.Transport ! Zone.Vehicle.TryDeploymentChange(obj, deploy_state)
}
obj
case obj =>
log.error(s"DeployRequest: ${player.Name} expected a vehicle, but found a ${obj.Definition.Name} instead")
obj
}
.orElse {
log.error(s"DeployRequest: ${player.Name} can not find entity $vehicle_guid")
avatarActor ! AvatarActor.SetVehicle(None) //todo is this safe
None
}
} else {
log.warn(s"DeployRequest: ${player.Name} does not own the deploying $vehicle_guid object")
}
}
/* messages */

View file

@ -1080,4 +1080,22 @@ object WorldSession {
task
)
}
def CallBackForTask(task: TaskBundle, sendTo: ActorRef, pass: Any, replyTo: ActorRef): TaskBundle = {
TaskBundle(
new StraightforwardTask() {
private val localDesc = task.description()
private val destination = sendTo
private val passMsg = pass
override def description(): String = s"callback for tasking $localDesc"
def action() : Future[Any] = {
destination.tell(passMsg, replyTo)
Future(this)
}
},
task
)
}
}

View file

@ -27,6 +27,7 @@ import net.psforever.objects.vital._
import net.psforever.types.{ExoSuitType, ImplantType, PlanetSideEmpire, Vector3}
import net.psforever.types._
import net.psforever.objects.serverobject.llu.{CaptureFlagDefinition, CaptureFlagSocketDefinition}
import net.psforever.objects.serverobject.zipline.GenericTeleportationDefinition
import scala.annotation.switch
@ -1277,6 +1278,10 @@ object GlobalDefinitions {
val targeting_laser_dispenser = new OrderTerminalDefinition(851)
val stationaryteleportpad = new GenericTeleportationDefinition(836)
val zipline = new GenericTeleportationDefinition(1047)
/*
Buildings
*/
@ -1294,29 +1299,28 @@ object GlobalDefinitions {
val vanu_core = new BuildingDefinition(932)
//the following group borrows the object id from entity mainbase1
val ground_bldg_a = new BuildingDefinition(474)
val ground_bldg_b = new BuildingDefinition(474)
val ground_bldg_c = new BuildingDefinition(474)
val ground_bldg_d = new BuildingDefinition(474)
val ground_bldg_e = new BuildingDefinition(474)
val ground_bldg_f = new BuildingDefinition(474)
val ground_bldg_g = new BuildingDefinition(474)
val ground_bldg_h = new BuildingDefinition(474)
val ground_bldg_i = new BuildingDefinition(474)
val ground_bldg_j = new BuildingDefinition(474)
val ground_bldg_z = new BuildingDefinition(474)
val ceiling_bldg_a = new BuildingDefinition(474)
val ceiling_bldg_b = new BuildingDefinition(474)
val ceiling_bldg_c = new BuildingDefinition(474)
val ceiling_bldg_d = new BuildingDefinition(474)
val ceiling_bldg_e = new BuildingDefinition(474)
val ceiling_bldg_f = new BuildingDefinition(474)
val ceiling_bldg_g = new BuildingDefinition(474)
val ceiling_bldg_h = new BuildingDefinition(474)
val ceiling_bldg_i = new BuildingDefinition(474)
val ceiling_bldg_j = new BuildingDefinition(474)
val ceiling_bldg_z = new BuildingDefinition(474)
val ground_bldg_a = new BuildingDefinition(373)
val ground_bldg_b = new BuildingDefinition(374)
val ground_bldg_c = new BuildingDefinition(375)
val ground_bldg_d = new BuildingDefinition(376)
val ground_bldg_e = new BuildingDefinition(377)
val ground_bldg_f = new BuildingDefinition(378)
val ground_bldg_g = new BuildingDefinition(379)
val ground_bldg_h = new BuildingDefinition(380)
val ground_bldg_i = new BuildingDefinition(381)
val ground_bldg_j = new BuildingDefinition(382)
val ground_bldg_z = new BuildingDefinition(383)
val ceiling_bldg_a = new BuildingDefinition(159)
val ceiling_bldg_b = new BuildingDefinition(160)
val ceiling_bldg_c = new BuildingDefinition(161)
val ceiling_bldg_d = new BuildingDefinition(162)
val ceiling_bldg_e = new BuildingDefinition(163)
val ceiling_bldg_f = new BuildingDefinition(164)
val ceiling_bldg_g = new BuildingDefinition(165)
val ceiling_bldg_h = new BuildingDefinition(166)
val ceiling_bldg_i = new BuildingDefinition(167)
val ceiling_bldg_j = new BuildingDefinition(168)
val ceiling_bldg_z = new BuildingDefinition(169)
val mainbase1 = new BuildingDefinition(474)

View file

@ -33,7 +33,7 @@ class DeployableToolbox {
* keys: categories, values: quantity storage object
*/
private val categoryCounts =
DeployableCategory.values.toSeq.map(value => { value -> new DeployableToolbox.Bin }).toMap
DeployableCategory.values.map(value => { value -> new DeployableToolbox.Bin }).toMap
/**
* a map of bins for keeping track of the quantities of individual deployables
@ -46,7 +46,7 @@ class DeployableToolbox {
* keys: categories, values: deployable objects
*/
private val deployableLists =
DeployableCategory.values.toSeq
DeployableCategory.values
.map(value => { value -> mutable.ListBuffer[DeployableToolbox.AcceptableDeployable]() })
.toMap
@ -73,7 +73,7 @@ class DeployableToolbox {
}
}
def UpdateMaxCounts(certifications: Set[Certification]) = {
def UpdateMaxCounts(certifications: Set[Certification]): Unit = {
DeployableToolbox.UpdateMaxCounts(deployableCounts, categoryCounts, certifications)
}
@ -247,7 +247,7 @@ class DeployableToolbox {
* @param category the target category
* @return any deployable that is found
*/
def DisplaceFirst(category: DeployableCategory.Value): Option[DeployableToolbox.AcceptableDeployable] = {
def DisplaceFirst(category: DeployableCategory): Option[DeployableToolbox.AcceptableDeployable] = {
val categoryList = deployableLists(category)
if (categoryList.nonEmpty) {
val found = categoryList.remove(0)
@ -294,7 +294,7 @@ class DeployableToolbox {
* @param filter the type of deployable
* @return a list of globally unique identifiers that should be valid for the current zone
*/
def Category(filter: DeployableCategory.Value): List[PlanetSideGUID] = {
def Category(filter: DeployableCategory): List[PlanetSideGUID] = {
deployableLists(filter).map(_.GUID).toList
}
@ -478,7 +478,7 @@ object DeployableToolbox {
*/
private def UpdateMaxCounts(
counts: Map[DeployedItem.Value, DeployableToolbox.Bin],
categories: Map[DeployableCategory.Value, DeployableToolbox.Bin],
categories: Map[DeployableCategory, DeployableToolbox.Bin],
certifications: Set[Certification]
): Unit = {
import Certification._

View file

@ -77,18 +77,18 @@ object Deployable {
}
object Category {
def Of(item: DeployedItem.Value): DeployableCategory.Value = deployablesToCategories(item)
def Of(item: DeployedItem.Value): DeployableCategory = deployablesToCategories(item)
def Includes(category: DeployableCategory.Value): List[DeployedItem.Value] = {
def Includes(category: DeployableCategory): List[DeployedItem.Value] = {
(for {
(ce: DeployedItem.Value, cat: DeployableCategory.Value) <- deployablesToCategories
(ce: DeployedItem.Value, cat: DeployableCategory) <- deployablesToCategories
if cat == category
} yield ce) toList
}
def OfAll(): Map[DeployedItem.Value, DeployableCategory.Value] = deployablesToCategories
def OfAll(): Map[DeployedItem.Value, DeployableCategory] = deployablesToCategories
private val deployablesToCategories: Map[DeployedItem.Value, DeployableCategory.Value] = Map(
private val deployablesToCategories: Map[DeployedItem.Value, DeployableCategory] = Map(
DeployedItem.boomer -> DeployableCategory.Boomers,
DeployedItem.he_mine -> DeployableCategory.Mines,
DeployedItem.jammer_mine -> DeployableCategory.Mines,

View file

@ -1,8 +1,26 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.ce
object DeployableCategory extends Enumeration {
type Type = Value
abstract class DeployableCategory(val name: String)
val Boomers, Mines, SmallTurrets, Sensors, TankTraps, FieldTurrets, ShieldGenerators, Telepads = Value
object DeployableCategory {
case object None extends DeployableCategory(name = "None")
case object Boomers extends DeployableCategory(name = "Boomers")
case object Mines extends DeployableCategory(name = "Mines")
case object SmallTurrets extends DeployableCategory(name = "SmallTurrets")
case object Sensors extends DeployableCategory(name = "Sensors")
case object TankTraps extends DeployableCategory(name = "TankTraps")
case object FieldTurrets extends DeployableCategory(name = "FieldTurrets")
case object ShieldGenerators extends DeployableCategory(name = "ShieldGenerators")
case object Telepads extends DeployableCategory(name = "Telepads")
val values: Seq[DeployableCategory] = Seq(None, Boomers, Mines, SmallTurrets, Sensors, TankTraps, FieldTurrets, ShieldGenerators, Telepads)
}

View file

@ -19,15 +19,15 @@ object DeployAnimation extends Enumeration {
}
trait BaseDeployableDefinition {
private var category: DeployableCategory.Value = DeployableCategory.Boomers
private var category: DeployableCategory = DeployableCategory.None
private var deployTime: Long = (1 second).toMillis //ms
var deployAnimation: DeployAnimation.Value = DeployAnimation.None
def Item: DeployedItem.Value
def DeployCategory: DeployableCategory.Value = category
def DeployCategory: DeployableCategory = category
def DeployCategory_=(cat: DeployableCategory.Value): DeployableCategory.Value = {
def DeployCategory_=(cat: DeployableCategory): DeployableCategory = {
category = cat
DeployCategory
}

View file

@ -5,6 +5,7 @@ import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.definition.converter.{ObjectCreateConverter, PacketConverter}
import net.psforever.objects.geometry.GeometryForm
import net.psforever.objects.geometry.d3.VolumetricGeometry
import net.psforever.objects.serverobject.deploy.{Interference, InterferenceRange}
import net.psforever.types.OxygenState
/**
@ -121,5 +122,10 @@ abstract class ObjectDefinition(private val objectId: Int)
*/
var maxForwardSpeed: Float = 0f
/**
* na
*/
var interference: InterferenceRange = Interference.AllowAll
def ObjectId: Int = objectId
}

View file

@ -8,6 +8,7 @@ import net.psforever.objects.definition.converter.{FieldTurretConverter, Interna
import net.psforever.objects.equipment.{EffectTarget, TargetValidation}
import net.psforever.objects.geometry.GeometryForm
import net.psforever.objects.geometry.d3.VolumetricGeometry
import net.psforever.objects.serverobject.deploy.InterferenceRange
import net.psforever.objects.serverobject.mount.{MountInfo, SeatDefinition}
import net.psforever.objects.serverobject.turret.{AutoChecks, AutoCooldowns, AutoRanges, Automation, TurretUpgrade}
import net.psforever.objects.vital.{CollisionXYData, CollisionZData, ComplexDeployableResolutions, SimpleResolutions}
@ -40,6 +41,7 @@ object GlobalDefinitionsDeployable {
boomer.DeployCategory = DeployableCategory.Boomers
boomer.DeployTime = Duration.create(1000, "ms")
boomer.deployAnimation = DeployAnimation.Standard
boomer.interference = InterferenceRange(main = 0.2f)
boomer.innateDamage = new DamageWithPosition {
CausesDamageType = DamageType.Splash
SympatheticExplosion = true
@ -62,6 +64,7 @@ object GlobalDefinitionsDeployable {
he_mine.Repairable = false
he_mine.DeployTime = Duration.create(1000, "ms")
he_mine.deployAnimation = DeployAnimation.Standard
he_mine.interference = InterferenceRange(main = 7f, sharedGroupId = 1, shared = 7f, deployables = 0.1f)
he_mine.triggerRadius = 3f
he_mine.innateDamage = new DamageWithPosition {
CausesDamageType = DamageType.Splash
@ -85,6 +88,7 @@ object GlobalDefinitionsDeployable {
jammer_mine.Repairable = false
jammer_mine.DeployTime = Duration.create(1000, "ms")
jammer_mine.deployAnimation = DeployAnimation.Standard
jammer_mine.interference = InterferenceRange(main = 7f, sharedGroupId = 1, shared = 7f, deployables = 0.1f)
jammer_mine.DetonateOnJamming = false
jammer_mine.triggerRadius = 3f
jammer_mine.innateDamage = new DamageWithPosition {
@ -133,6 +137,7 @@ object GlobalDefinitionsDeployable {
spitfire_turret.DeployTime = Duration.create(5000, "ms")
spitfire_turret.Model = ComplexDeployableResolutions.calculate
spitfire_turret.deployAnimation = DeployAnimation.Standard
spitfire_turret.interference = InterferenceRange(main = 25f, sharedGroupId = 2, shared = 25f, deployables = 0.1f)
spitfire_turret.AutoFire = Automation(
AutoRanges(
detection = 75f,
@ -176,6 +181,7 @@ object GlobalDefinitionsDeployable {
spitfire_cloaked.DeployCategory = DeployableCategory.SmallTurrets
spitfire_cloaked.DeployTime = Duration.create(5000, "ms")
spitfire_cloaked.deployAnimation = DeployAnimation.Standard
spitfire_cloaked.interference = InterferenceRange(main = 25f, sharedGroupId = 2, shared = 25f, deployables = 0.1f)
spitfire_cloaked.Model = ComplexDeployableResolutions.calculate
spitfire_cloaked.AutoFire = Automation(
AutoRanges(
@ -226,6 +232,7 @@ object GlobalDefinitionsDeployable {
spitfire_aa.DeployCategory = DeployableCategory.SmallTurrets
spitfire_aa.DeployTime = Duration.create(5000, "ms")
spitfire_aa.deployAnimation = DeployAnimation.Standard
spitfire_aa.interference = InterferenceRange(main = 25f, sharedGroupId = 2, shared = 25f, deployables = 0.1f)
spitfire_aa.Model = ComplexDeployableResolutions.calculate
spitfire_aa.AutoFire = Automation(
AutoRanges(
@ -263,6 +270,7 @@ object GlobalDefinitionsDeployable {
motionalarmsensor.RepairIfDestroyed = false
motionalarmsensor.DeployTime = Duration.create(1000, "ms")
motionalarmsensor.deployAnimation = DeployAnimation.Standard
motionalarmsensor.interference = InterferenceRange(main = 25f, deployables = 0.1f)
motionalarmsensor.Geometry = sensor
sensor_shield.Name = "sensor_shield"
@ -273,6 +281,7 @@ object GlobalDefinitionsDeployable {
sensor_shield.RepairIfDestroyed = false
sensor_shield.DeployTime = Duration.create(5000, "ms")
sensor_shield.deployAnimation = DeployAnimation.Standard
sensor_shield.interference = InterferenceRange(main = 20f, deployables = 0.1f)
sensor_shield.Geometry = sensor
tank_traps.Name = "tank_traps"
@ -284,6 +293,8 @@ object GlobalDefinitionsDeployable {
tank_traps.DeployCategory = DeployableCategory.TankTraps
tank_traps.DeployTime = Duration.create(6000, "ms")
tank_traps.deployAnimation = DeployAnimation.Fdu
tank_traps.interference = InterferenceRange(main = 3.5f, sharedGroupId = 3, shared = 60f, deployables = 3f)
//todo what is tank_traps interference2 60
//tank_traps do not explode
tank_traps.innateDamage = new DamageWithPosition {
CausesDamageType = DamageType.One
@ -318,6 +329,7 @@ object GlobalDefinitionsDeployable {
portable_manned_turret.DeployCategory = DeployableCategory.FieldTurrets
portable_manned_turret.DeployTime = Duration.create(6000, "ms")
portable_manned_turret.deployAnimation = DeployAnimation.Fdu
portable_manned_turret.interference = InterferenceRange(main = 60f, sharedGroupId = 3, shared = 40f, deployables = 2.5f)
portable_manned_turret.Model = ComplexDeployableResolutions.calculate
portable_manned_turret.RadiationShielding = 0.5f
portable_manned_turret.innateDamage = new DamageWithPosition {
@ -351,6 +363,7 @@ object GlobalDefinitionsDeployable {
portable_manned_turret_nc.DeployCategory = DeployableCategory.FieldTurrets
portable_manned_turret_nc.DeployTime = Duration.create(6000, "ms")
portable_manned_turret_nc.deployAnimation = DeployAnimation.Fdu
portable_manned_turret_nc.interference = InterferenceRange(main = 60f, sharedGroupId = 3, shared = 40f, deployables = 2.5f)
portable_manned_turret_nc.Model = ComplexDeployableResolutions.calculate
portable_manned_turret_nc.innateDamage = new DamageWithPosition {
CausesDamageType = DamageType.One
@ -383,6 +396,7 @@ object GlobalDefinitionsDeployable {
portable_manned_turret_tr.DeployCategory = DeployableCategory.FieldTurrets
portable_manned_turret_tr.DeployTime = Duration.create(6000, "ms")
portable_manned_turret_tr.deployAnimation = DeployAnimation.Fdu
portable_manned_turret_tr.interference = InterferenceRange(main = 60f, sharedGroupId = 3, shared = 40f, deployables = 2.5f)
portable_manned_turret_tr.Model = ComplexDeployableResolutions.calculate
portable_manned_turret_tr.innateDamage = new DamageWithPosition {
CausesDamageType = DamageType.One
@ -415,6 +429,7 @@ object GlobalDefinitionsDeployable {
portable_manned_turret_vs.DeployCategory = DeployableCategory.FieldTurrets
portable_manned_turret_vs.DeployTime = Duration.create(6000, "ms")
portable_manned_turret_vs.deployAnimation = DeployAnimation.Fdu
portable_manned_turret_vs.interference = InterferenceRange(main = 60f, sharedGroupId = 3, shared = 40f, deployables = 2.5f)
portable_manned_turret_vs.Model = ComplexDeployableResolutions.calculate
portable_manned_turret_vs.innateDamage = new DamageWithPosition {
CausesDamageType = DamageType.One
@ -437,6 +452,7 @@ object GlobalDefinitionsDeployable {
deployable_shield_generator.RepairIfDestroyed = false
deployable_shield_generator.DeployTime = Duration.create(6000, "ms")
deployable_shield_generator.deployAnimation = DeployAnimation.Fdu
deployable_shield_generator.interference = InterferenceRange(main = 125f, sharedGroupId = 3, shared = 60f, deployables = 2f)
deployable_shield_generator.Model = ComplexDeployableResolutions.calculate
deployable_shield_generator.Geometry = GeometryForm.representByCylinder(radius = 0.6562f, height = 2.17188f)

View file

@ -6,6 +6,7 @@ import net.psforever.objects.avatar.Certification
import net.psforever.objects.equipment.EffectTarget
import net.psforever.objects.geometry.GeometryForm
import net.psforever.objects.geometry.d3.VolumetricGeometry
import net.psforever.objects.serverobject.deploy.InterferenceRange
import net.psforever.objects.serverobject.doors.InteriorDoorField
import net.psforever.objects.serverobject.mount.{MountInfo, SeatDefinition}
import net.psforever.objects.serverobject.pad.VehicleSpawnPadDefinition
@ -934,5 +935,11 @@ object GlobalDefinitionsMiscellaneous {
obbasemesh.Descriptor = "orbital_shuttle_pad"
obbasemesh.Damageable = false
obbasemesh.Repairable = false
stationaryteleportpad.Name = "stationaryteleportpad"
stationaryteleportpad.interference = InterferenceRange(deployables = 5.5f)
zipline.Name = "zipline"
zipline.interference = InterferenceRange(deployables = 5.5f)
}
}

View file

@ -6,6 +6,7 @@ import net.psforever.objects.definition.converter._
import net.psforever.objects.geometry.GeometryForm
import net.psforever.objects.inventory.InventoryTile
import net.psforever.objects.GlobalDefinitions
import net.psforever.objects.serverobject.deploy.InterferenceRange
import net.psforever.objects.serverobject.mount._
import net.psforever.objects.vehicles.{DestroyedVehicle, UtilityType, VehicleSubsystemEntry}
import net.psforever.objects.vital.base.DamageType
@ -969,6 +970,7 @@ object GlobalDefinitionsVehicle {
ams.Deployment = true
ams.DeployTime = 2000
ams.UndeployTime = 2000
ams.interference = InterferenceRange(main = 125f, sharedGroupId = 3, shared = 30f)
ams.DeconstructionTime = Some(20 minutes)
ams.AutoPilotSpeeds = (18, 6)
ams.Packet = utilityConverter

View file

@ -0,0 +1,128 @@
// Copyright (c) 2024 PSForever
package net.psforever.objects.serverobject.deploy
import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.definition.{DeployableDefinition, ObjectDefinition}
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.zones.Zone
import net.psforever.types.{DriveState, Vector3}
import scala.annotation.unused
/**
* Block the deployment of certain entities within a certain distance.
* Deployable vehicles and combat engineer entities both have a deployment condition that can be influenced by these ranges.
* Vehicles of an object type block other vehicles of that object type.
* Combat engineering entities block combat engineering entities of the same category.
* @param main distance between which this kind of entity blocks itself (m)
* @param sharedGroupId identifier for the similar entity group
* @param shared distance between entities that belong to the similar group block one another (m)
* @param deployables distance between which this entity may block deployment of combat engineering entities (m);
* defaults to 0
*/
final case class InterferenceRange(main: Float = 0f, sharedGroupId: Int = 0, shared: Float = 0f, deployables: Float = 0f) {
assert(
main > -1f && deployables > -1f,
"if set, interference range must be positive non-zero float value"
)
assert(
sharedGroupId == 0 || shared > 0f,
"if set, a shared group of a non-zero id should have a positive non-zero float value"
)
}
object Interference {
final val AllowAll: InterferenceRange = InterferenceRange()
final val MaxRange: Float = 125f
/**
* When two entities connected by similarity or distance exert influence on one another
* that stops the later-acting entity from manifesting
* or disables certain behaviors from the later-acting entity.
* Only dynamic entities are examined.
* Static entities like facility amenities or zone-specific elements that exert interference
* must manage their interactions through other methods.
* @param zone game world in which this test will be conducted;
* entity should be `ZoneAware`, but it may not be set correctly during this part of its internal process
* @param obj entity that may be interfered with
* @return a different entity that causes the test entity to suffer interference
*/
def Test(zone: Zone, obj: PlanetSideGameObject with FactionAffinity): Option[PlanetSideGameObject with FactionAffinity] = {
val (data, filterFunc) = SetupForTest(zone, obj)
data.find(filterFunc)
}
/**
* When two entities connected by similarity or distance exert influence on one another
* that stops the later-acting entity from manifesting
* or disables certain behaviors from the later-acting entity.
* @param zone game world in which this test will be conducted;
* entity should be `ZoneAware`, but it may not be set correctly during this part of its internal process
* @param obj entity that may be interfered with
* @return list of entities to run an interference test on, and
* predicate to determine which entities pass the test
*/
def SetupForTest(
zone: Zone,
obj: PlanetSideGameObject with FactionAffinity
): (List[PlanetSideGameObject with FactionAffinity], PlanetSideGameObject => Boolean) = {
val objectDefinition = obj.Definition
if (objectDefinition.interference eq Interference.AllowAll) {
(List(), interferenceTestNoResults) //no targets can block, and test will never pass
} else {
val position = obj.Position
val faction = obj.Faction
val sharedGroupId = objectDefinition.interference.sharedGroupId
val sector = zone.blockMap.sector(position, Interference.MaxRange)
val targets = (sector.deployableList ++ sector.vehicleList.filter(_.DeploymentState >= DriveState.Deploying))
.collect { case target: PlanetSideGameObject with FactionAffinity
if target.Faction != faction &&
(target.Definition.asInstanceOf[ObjectDefinition].interference ne Interference.AllowAll) =>
target
}
if (sharedGroupId != 0) {
(targets, interferenceTestWithSharedGroup(position, objectDefinition, sharedGroupId))
} else {
(targets, interferenceTestNoSharedGroup(position, objectDefinition))
}
}
}
private def interferenceTestNoResults(@unused p: PlanetSideGameObject): Boolean = false
private def interferenceTestNoSharedGroup(
position: Vector3,
objectDefinition: ObjectDefinition
): PlanetSideGameObject => Boolean = { p =>
val pDefinition = p.Definition
val objectInterference = objectDefinition.interference
lazy val distanceSq = Vector3.DistanceSquared(position, p.Position)
if (pDefinition == objectDefinition) {
distanceSq < objectInterference.main * objectInterference.main
} else if (pDefinition.isInstanceOf[DeployableDefinition]) {
distanceSq < objectInterference.deployables * objectInterference.deployables
} else {
false
}
}
private def interferenceTestWithSharedGroup(
position: Vector3,
objectDefinition: ObjectDefinition,
sharedGroupId: Int
): PlanetSideGameObject => Boolean = { p =>
val pDefinition = p.Definition
val objectInterference = objectDefinition.interference
lazy val distanceSq = Vector3.DistanceSquared(position, p.Position)
if (pDefinition == objectDefinition) {
distanceSq < objectInterference.main * objectInterference.main
} else if (sharedGroupId == pDefinition.interference.sharedGroupId) {
distanceSq < objectInterference.shared * objectInterference.shared
} else if (pDefinition.isInstanceOf[DeployableDefinition]) {
distanceSq < objectInterference.deployables * objectInterference.deployables
} else {
false
}
}
}

View file

@ -0,0 +1,12 @@
// Copyright (c) 2024 PSForever
package net.psforever.objects.serverobject.zipline
import net.psforever.objects.definition.ObjectDefinition
/**
* The definition for any generic teleportation entity.
* Two entities are described by this object definition - zip lines and the teleportation rings, both in the caverns.
* Entities of these objects are environmental, like facilities, do not get constructed by the user.
* The said entities also do not require being configured at login or zone load.
*/
class GenericTeleportationDefinition(private val objectId: Int) extends ObjectDefinition(objectId)

View file

@ -1317,6 +1317,10 @@ object Zone {
final case class CanNotSpawn(zone: Zone, vehicle: Vehicle, reason: String)
final case class CanNotDespawn(zone: Zone, vehicle: Vehicle, reason: String)
final case class TryDeploymentChange(vehicle: Vehicle, toDeployState: DriveState.Value)
final case class CanNotDeploy(zone: Zone, vehicle: Vehicle, toDeployState: DriveState.Value, reason: String)
}
object HotSpot {

View file

@ -5,6 +5,7 @@ import akka.actor.Actor
import net.psforever.objects.Player
import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.ce.Deployable
import net.psforever.objects.serverobject.deploy.Interference
import net.psforever.objects.sourcing.ObjectSource
import net.psforever.objects.vehicles.MountedWeapons
import net.psforever.objects.vital.SpawningActivity
@ -28,7 +29,7 @@ class ZoneDeployableActor(
def receive: Receive = {
case Zone.Deployable.Build(obj) =>
if (DeployableBuild(obj, deployableList)) {
if (DeployableBuild(zone, obj, deployableList)) {
obj.Zone = zone
obj match {
case mounting: MountedWeapons =>
@ -40,11 +41,10 @@ class ZoneDeployableActor(
.foreach { guid =>
turretToMount.put(guid, dguid)
}
case _ => ;
case _ => ()
}
obj.Definition.Initialize(obj, context)
zone.actor ! ZoneActor.AddToBlockMap(obj, obj.Position)
//obj.History(EntitySpawn(SourceEntry(obj), obj.Zone))
obj.LogActivity(SpawningActivity(ObjectSource(obj), zone.Number, None))
obj.Actor ! Zone.Deployable.Setup()
} else {
log.warn(s"failed to build a ${obj.Definition.Name}")
@ -52,7 +52,7 @@ class ZoneDeployableActor(
}
case Zone.Deployable.BuildByOwner(obj, owner, tool) =>
if (DeployableBuild(obj, deployableList)) {
if (DeployableBuild(zone, obj, deployableList)) {
obj.Zone = zone
obj match {
case mounting: MountedWeapons =>
@ -64,10 +64,9 @@ class ZoneDeployableActor(
.foreach { guid =>
turretToMount.put(guid, dguid)
}
case _ => ;
case _ => ()
}
obj.Definition.Initialize(obj, context)
zone.actor ! ZoneActor.AddToBlockMap(obj, obj.Position)
obj.LogActivity(SpawningActivity(ObjectSource(obj), zone.Number, None))
owner.Actor ! Player.BuildDeployable(obj, tool)
} else {
@ -76,50 +75,54 @@ class ZoneDeployableActor(
}
case Zone.Deployable.Dismiss(obj) =>
if (DeployableDismiss(obj, deployableList)) {
if (DeployableDismiss(zone, obj, deployableList)) {
obj match {
case _: MountedWeapons =>
val dguid = obj.GUID.guid
turretToMount.filterInPlace { case (_, guid) => guid != dguid }
case _ => ;
case _ => ()
}
obj.Actor ! Zone.Deployable.IsDismissed(obj)
obj.Definition.Uninitialize(obj, context)
obj.ClearHistory()
zone.actor ! ZoneActor.RemoveFromBlockMap(obj)
}
case Zone.Deployable.IsBuilt(_) => ;
case Zone.Deployable.IsBuilt(_) => ()
case Zone.Deployable.IsDismissed(_) => ;
case Zone.Deployable.IsDismissed(_) => ()
case _ => ;
case _ => ()
}
}
object ZoneDeployableActor {
def DeployableBuild(
obj: Deployable,
deployableList: ListBuffer[Deployable]
): Boolean = {
deployableList.find(d => d == obj) match {
case Some(_) =>
false
case None =>
zone: Zone,
obj: Deployable,
deployableList: ListBuffer[Deployable]
): Boolean = {
val position = obj.Position
deployableList.find(_ eq obj) match {
case None if Interference.Test(zone, obj).isEmpty =>
deployableList += obj
zone.actor ! ZoneActor.AddToBlockMap(obj, position)
true
case _ =>
false
}
}
def DeployableDismiss(
obj: Deployable,
deployableList: ListBuffer[Deployable]
): Boolean = {
zone: Zone,
obj: Deployable,
deployableList: ListBuffer[Deployable]
): Boolean = {
recursiveFindDeployable(deployableList.iterator, obj) match {
case None =>
false
case Some(index) =>
deployableList.remove(index)
zone.actor ! ZoneActor.RemoveFromBlockMap(obj)
true
}
}

View file

@ -3,9 +3,11 @@ package net.psforever.objects.zones
import akka.actor.Actor
import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.definition.VehicleDefinition
import net.psforever.objects.serverobject.deploy.{Deployment, Interference}
import net.psforever.objects.vital.InGameHistory
import net.psforever.objects.{Default, Vehicle}
import net.psforever.types.Vector3
import net.psforever.types.{DriveState, PlanetSideEmpire, Vector3}
import scala.annotation.tailrec
import scala.collection.mutable
@ -13,27 +15,14 @@ import scala.collection.mutable
/**
* Synchronize management of the list of `Vehicles` maintained by some `Zone`.
*/
//COMMENTS IMPORTED FROM FORMER VehicleContextActor:
/*
* Provide a context for a `Vehicle` `Actor` - the `VehicleControl`.<br>
* <br>
* A vehicle can be passed between different zones and, therefore, does not belong to the zone.
* A vehicle cna be given to different players and can persist and change though players have gone.
* Therefore, also does not belong to `WorldSessionActor`.
* A vehicle must anchored to something that exists outside of the `InterstellarCluster` and its agents.<br>
* <br>
* The only purpose of this `Actor` is to allow vehicles to borrow a context for the purpose of `Actor` creation.
* It is also be allowed to be responsible for cleaning up that context.
* (In reality, it can be cleaned up anywhere a `PoisonPill` can be sent.)<br>
* <br>
* This `Actor` is intended to sit on top of the event system that handles broadcast messaging.
*/
class ZoneVehicleActor(
zone: Zone,
vehicleList: mutable.ListBuffer[Vehicle],
turretToMount: mutable.HashMap[Int, Int]
) extends Actor {
//private[this] val log = org.log4s.getLogger
private val log = org.log4s.getLogger(s"${zone.id}-vehicles")
private var temporaryInterference: Seq[(Vector3, PlanetSideEmpire.Value, VehicleDefinition)] = Seq()
def receive: Receive = {
case Zone.Vehicle.Spawn(vehicle) =>
@ -73,19 +62,62 @@ class ZoneVehicleActor(
vehicle.ClearHistory()
zone.actor ! ZoneActor.RemoveFromBlockMap(vehicle)
sender() ! Zone.Vehicle.HasDespawned(zone, vehicle)
case None => ;
case None =>
sender() ! Zone.Vehicle.CanNotDespawn(zone, vehicle, "can not find")
}
case Zone.Vehicle.HasDespawned(_, _) => ;
case Zone.Vehicle.TryDeploymentChange(vehicle, toDeployState)
if toDeployState == DriveState.Deploying &&
(ZoneVehicleActor.temporaryInterferenceTest(vehicle, temporaryInterference) || Interference.Test(zone, vehicle).nonEmpty) =>
sender() ! Zone.Vehicle.CanNotDeploy(zone, vehicle, toDeployState, "blocked by a nearby entity")
case Zone.Vehicle.CanNotDespawn(_, _, _) => ;
case Zone.Vehicle.TryDeploymentChange(vehicle, toDeployState)
if toDeployState == DriveState.Deploying =>
tryAddToInterferenceField(vehicle.Position, vehicle.Faction, vehicle.Definition)
vehicle.Actor.tell(Deployment.TryDeploymentChange(toDeployState), sender())
case _ => ;
case Zone.Vehicle.TryDeploymentChange(vehicle, toDeployState) =>
vehicle.Actor.tell(Deployment.TryDeploymentChange(toDeployState), sender())
case Zone.Vehicle.HasDespawned(_, _) => ()
case Zone.Vehicle.CanNotDespawn(_, _, _) => ()
case Zone.Vehicle.CanNotDeploy(_, vehicle, _, reason) => ()
val pos = vehicle.Position
val driverMoniker = vehicle.Seats.headOption.flatMap(_._2.occupant).map(_.Name).getOrElse("Driver")
log.warn(s"$driverMoniker's ${vehicle.Definition.Name} can not deploy in ${zone.id} because $reason")
temporaryInterference = temporaryInterference.filterNot(_._1 == pos)
case ZoneVehicleActor.ClearInterference(pos) =>
temporaryInterference = temporaryInterference.filterNot(_._1 == pos)
case _ => ()
}
private def tryAddToInterferenceField(
position: Vector3,
faction: PlanetSideEmpire.Value,
definition: VehicleDefinition
): Boolean = {
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
val causesInterference = definition.interference ne Interference.AllowAll
if (causesInterference) {
temporaryInterference = temporaryInterference :+ (position, faction, definition)
context.system.scheduler.scheduleOnce(
definition.DeployTime.milliseconds,
self,
ZoneVehicleActor.ClearInterference(position)
)
}
causesInterference
}
}
object ZoneVehicleActor {
private case class ClearInterference(pos: Vector3)
@tailrec final def recursiveFindVehicle(iter: Iterator[Vehicle], target: Vehicle, index: Int = 0): Option[Int] = {
if (!iter.hasNext) {
None
@ -97,4 +129,26 @@ object ZoneVehicleActor {
}
}
}
private def temporaryInterferenceTest(
vehicle: Vehicle,
existingInterferences: Seq[(Vector3, PlanetSideEmpire.Value, VehicleDefinition)]
): Boolean = {
val vPosition = vehicle.Position
val vFaction = vehicle.Faction
val vDefinition = vehicle.Definition
if (vDefinition.interference eq Interference.AllowAll) {
false
} else {
existingInterferences
.collect { case (p, faction, d) if faction == vFaction => (p, d) }
.exists { case (position, definition) =>
val interference = definition.interference
(interference ne Interference.AllowAll) && {
lazy val distanceSq = Vector3.DistanceSquared(position, vPosition)
definition == vDefinition && distanceSq < interference.main * interference.main
}
}
}
}
}

View file

@ -15,9 +15,9 @@ object DriveState extends Enumeration {
val Undeploying = Value(1)
val Deploying = Value(2)
val Deployed = Value(3)
val UNK4 = Value(4) // to have Xtoolspar working
val UNK5 = Value(5) // to have Xtoolspar working
val UNK6 = Value(6) // to have Xtoolspar working
val UNK4 = Value(4) // to have decoding tool working
val UNK5 = Value(5) // to have decoding tool working
val UNK6 = Value(6) // to have decoding tool working
val State7 = Value(7) //unknown; not encountered on a vehicle that can deploy; functions like Mobile
val State127 = Value(127) //unknown