diff --git a/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala b/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala index 7c493e365..812bf74af 100644 --- a/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala @@ -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 => diff --git a/src/main/scala/net/psforever/actors/session/normal/NormalMode.scala b/src/main/scala/net/psforever/actors/session/normal/NormalMode.scala index 9c49e8dd5..d6fd141e3 100644 --- a/src/main/scala/net/psforever/actors/session/normal/NormalMode.scala +++ b/src/main/scala/net/psforever/actors/session/normal/NormalMode.scala @@ -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 => diff --git a/src/main/scala/net/psforever/actors/session/normal/VehicleLogic.scala b/src/main/scala/net/psforever/actors/session/normal/VehicleLogic.scala index 8d39fcc6b..f6fe40c83 100644 --- a/src/main/scala/net/psforever/actors/session/normal/VehicleLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/VehicleLogic.scala @@ -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 */ diff --git a/src/main/scala/net/psforever/login/WorldSession.scala b/src/main/scala/net/psforever/login/WorldSession.scala index f49d54d65..ae02c6f64 100644 --- a/src/main/scala/net/psforever/login/WorldSession.scala +++ b/src/main/scala/net/psforever/login/WorldSession.scala @@ -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 + ) + } } diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 190b098bd..778f9abeb 100644 --- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -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) diff --git a/src/main/scala/net/psforever/objects/avatar/DeployableToolbox.scala b/src/main/scala/net/psforever/objects/avatar/DeployableToolbox.scala index 91451b4ea..2a6f4d23b 100644 --- a/src/main/scala/net/psforever/objects/avatar/DeployableToolbox.scala +++ b/src/main/scala/net/psforever/objects/avatar/DeployableToolbox.scala @@ -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._ diff --git a/src/main/scala/net/psforever/objects/ce/Deployable.scala b/src/main/scala/net/psforever/objects/ce/Deployable.scala index 65387285b..0a4664945 100644 --- a/src/main/scala/net/psforever/objects/ce/Deployable.scala +++ b/src/main/scala/net/psforever/objects/ce/Deployable.scala @@ -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, diff --git a/src/main/scala/net/psforever/objects/ce/DeployableCategory.scala b/src/main/scala/net/psforever/objects/ce/DeployableCategory.scala index 0a40d1fc6..adc5eac27 100644 --- a/src/main/scala/net/psforever/objects/ce/DeployableCategory.scala +++ b/src/main/scala/net/psforever/objects/ce/DeployableCategory.scala @@ -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) } diff --git a/src/main/scala/net/psforever/objects/definition/DeployableDefinition.scala b/src/main/scala/net/psforever/objects/definition/DeployableDefinition.scala index 4f9308292..763cb49cd 100644 --- a/src/main/scala/net/psforever/objects/definition/DeployableDefinition.scala +++ b/src/main/scala/net/psforever/objects/definition/DeployableDefinition.scala @@ -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 } diff --git a/src/main/scala/net/psforever/objects/definition/ObjectDefinition.scala b/src/main/scala/net/psforever/objects/definition/ObjectDefinition.scala index c1b12cb21..4cd0e824c 100644 --- a/src/main/scala/net/psforever/objects/definition/ObjectDefinition.scala +++ b/src/main/scala/net/psforever/objects/definition/ObjectDefinition.scala @@ -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 } diff --git a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsDeployable.scala b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsDeployable.scala index e59828e23..da633d60a 100644 --- a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsDeployable.scala +++ b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsDeployable.scala @@ -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) diff --git a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala index f625b38f5..12868e001 100644 --- a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala +++ b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala @@ -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) } } diff --git a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsVehicle.scala b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsVehicle.scala index df3b9b793..c3dd0c9dd 100644 --- a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsVehicle.scala +++ b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsVehicle.scala @@ -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 diff --git a/src/main/scala/net/psforever/objects/serverobject/deploy/Interference.scala b/src/main/scala/net/psforever/objects/serverobject/deploy/Interference.scala new file mode 100644 index 000000000..16ba67795 --- /dev/null +++ b/src/main/scala/net/psforever/objects/serverobject/deploy/Interference.scala @@ -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 + } + } +} diff --git a/src/main/scala/net/psforever/objects/serverobject/zipline/GenericTeleportationDefinition.scala b/src/main/scala/net/psforever/objects/serverobject/zipline/GenericTeleportationDefinition.scala new file mode 100644 index 000000000..1f8a39f39 --- /dev/null +++ b/src/main/scala/net/psforever/objects/serverobject/zipline/GenericTeleportationDefinition.scala @@ -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) diff --git a/src/main/scala/net/psforever/objects/zones/Zone.scala b/src/main/scala/net/psforever/objects/zones/Zone.scala index 6e6d7aef2..3f71a937f 100644 --- a/src/main/scala/net/psforever/objects/zones/Zone.scala +++ b/src/main/scala/net/psforever/objects/zones/Zone.scala @@ -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 { diff --git a/src/main/scala/net/psforever/objects/zones/ZoneDeployableActor.scala b/src/main/scala/net/psforever/objects/zones/ZoneDeployableActor.scala index 728cbce85..3c4a92a53 100644 --- a/src/main/scala/net/psforever/objects/zones/ZoneDeployableActor.scala +++ b/src/main/scala/net/psforever/objects/zones/ZoneDeployableActor.scala @@ -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 } } diff --git a/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala b/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala index ebabcc10d..c1b7780be 100644 --- a/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala +++ b/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala @@ -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`.
- *
- * 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.
- *
- * 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.)
- *
- * 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 + } + } + } + } } diff --git a/src/main/scala/net/psforever/types/DriveState.scala b/src/main/scala/net/psforever/types/DriveState.scala index be192c369..7bd39043c 100644 --- a/src/main/scala/net/psforever/types/DriveState.scala +++ b/src/main/scala/net/psforever/types/DriveState.scala @@ -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