diff --git a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala index b6535686..b6407e45 100644 --- a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala @@ -2,8 +2,6 @@ package net.psforever.actors.session.support import akka.actor.{ActorContext, typed} -import net.psforever.objects.avatar.scoring.EquipmentStat - import scala.collection.mutable import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future @@ -11,6 +9,7 @@ import scala.concurrent.duration._ // import net.psforever.actors.session.{AvatarActor, ChatActor, SessionActor} import net.psforever.login.WorldSession.{CountAmmunition, CountGrenades, FindAmmoBoxThatUses, FindEquipmentStock, FindToolThatUses, PutEquipmentInInventoryOrDrop, PutNewEquipmentInInventoryOrDrop, RemoveOldEquipmentFromInventory} +import net.psforever.objects.avatar.scoring.EquipmentStat import net.psforever.objects.ballistics.{Projectile, ProjectileQuality} import net.psforever.objects.entity.SimpleWorldEntity import net.psforever.objects.equipment.{ChargeFireModeDefinition, Equipment, EquipmentSize, FireModeSwitch} @@ -22,6 +21,7 @@ import net.psforever.objects.serverobject.turret.FacilityTurret import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} import net.psforever.objects.vital.Vitality import net.psforever.objects.vital.base.{DamageResolution, DamageType} +import net.psforever.objects.vital.etc.OicwLilBuddyReason import net.psforever.objects.vital.interaction.DamageInteraction import net.psforever.objects.vital.projectile.ProjectileReason import net.psforever.objects.zones.{Zone, ZoneProjectile} @@ -1186,13 +1186,10 @@ private[support] class WeaponAndProjectileOperations( def HandleDamageProxyLittleBuddyExplosion(proxy: Projectile, orientation: Vector3, distance: Float): Unit = { //explosion - val obj = DummyExplodingEntity(proxy) + val obj = new DummyExplodingEntity(proxy, proxy.owner.Faction) obj.Position = obj.Position + orientation * distance - context.system.scheduler.scheduleOnce(500.milliseconds) { - val c = continent - val o = obj - Zone.serverSideDamage(c, o, Zone.explosionDamage(None, o.Position)) - } + val explosionFunc: ()=>Unit = WeaponAndProjectileOperations.detonateLittleBuddy(continent, obj, proxy, proxy.owner) + context.system.scheduler.scheduleOnce(500.milliseconds) { explosionFunc() } } /** @@ -1246,3 +1243,50 @@ private[support] class WeaponAndProjectileOperations( } } } + +object WeaponAndProjectileOperations { + /** + * Preparation for explosion damage that utilizes the Scorpion's little buddy sub-projectiles. + * The main difference from "normal" server-side explosion + * is that the owner of the projectile must be clarified explicitly. + * @see `Zone::serverSideDamage` + * @param zone where the explosion is taking place + * (`source` contains the coordinate location) + * @param source a game object that represents the source of the explosion + * @param owner who or what to accredit damage from the explosion to; + * clarifies a normal `SourceEntry(source)` accreditation + */ + private def detonateLittleBuddy( + zone: Zone, + source: PlanetSideGameObject with FactionAffinity with Vitality, + proxy: Projectile, + owner: SourceEntry + )(): Unit = { + Zone.serverSideDamage(zone, source, littleBuddyExplosionDamage(owner, proxy.id, source.Position)) + } + + /** + * Preparation for explosion damage that utilizes the Scorpion's little buddy sub-projectiles. + * The main difference from "normal" server-side explosion + * is that the owner of the projectile must be clarified explicitly. + * The sub-projectiles will be the product of a normal projectile rather than a standard game object + * so a custom `source` entity must wrap around it and fulfill the requirements of the field. + * @see `Zone::explosionDamage` + * @param owner who or what to accredit damage from the explosion to + * @param explosionPosition where the explosion will be positioned in the game world + * @param source a game object that represents the source of the explosion + * @param target a game object that is affected by the explosion + * @return a `DamageInteraction` object + */ + private def littleBuddyExplosionDamage( + owner: SourceEntry, + projectileId: Long, + explosionPosition: Vector3 + ) + ( + source: PlanetSideGameObject with FactionAffinity with Vitality, + target: PlanetSideGameObject with FactionAffinity with Vitality + ): DamageInteraction = { + DamageInteraction(SourceEntry(target), OicwLilBuddyReason(owner, projectileId, target.DamageModel), explosionPosition) + } +} diff --git a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala index 35ef5494..60dc17df 100644 --- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala @@ -180,13 +180,14 @@ class ZoningOperations( sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 0)) // disable festive backpacks //find and reclaim own deployables, if any - val foundDeployables = - continent.DeployableList.filter(obj => obj.OwnerName.contains(player.Name) && obj.Health > 0) - foundDeployables.foreach(obj => { - if (avatar.deployables.AddOverLimit(obj)) { + val foundDeployables = continent.DeployableList.filter { + case _: BoomerDeployable => false //if we do find boomers for any reason, ignore them + case dobj => dobj.OwnerName.contains(player.Name) && dobj.Health > 0 + } + foundDeployables.collect { + case obj if avatar.deployables.AddOverLimit(obj) => obj.Actor ! Deployable.Ownership(player) - } - }) + } //render deployable objects val (turrets, normal) = continent.DeployableList.partition(obj => DeployableToolbox.UnifiedType(obj.Definition.Item) == DeployedItem.portable_manned_turret diff --git a/src/main/scala/net/psforever/objects/DummyExplodingEntity.scala b/src/main/scala/net/psforever/objects/DummyExplodingEntity.scala index 58183625..a10c474c 100644 --- a/src/main/scala/net/psforever/objects/DummyExplodingEntity.scala +++ b/src/main/scala/net/psforever/objects/DummyExplodingEntity.scala @@ -1,11 +1,12 @@ // Copyright (c) 2022 PSForever package net.psforever.objects +import net.psforever.objects.ballistics.Projectile import net.psforever.objects.definition.{ObjectDefinition, ProjectileDefinition} import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.vital.resolution.{DamageAndResistance, DamageResistanceModel} import net.psforever.objects.vital.{Vitality, VitalityDefinition} -import net.psforever.types.{PlanetSideEmpire, Vector3} +import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3} class DummyExplodingEntity( private val obj: PlanetSideGameObject, @@ -14,7 +15,7 @@ class DummyExplodingEntity( extends PlanetSideGameObject with FactionAffinity with Vitality { - override def GUID = obj.GUID + override def GUID: PlanetSideGUID = obj.GUID override def Position: Vector3 = { if (super.Position == Vector3.Zero) { @@ -41,20 +42,29 @@ class DummyExplodingEntity( def DamageModel: DamageAndResistance = DummyExplodingEntity.DefaultDamageResistanceModel def Definition: ObjectDefinition with VitalityDefinition = { - new DefinitionWrappedInVitality(obj.Definition) + new DefinitionWrappedInVitality(obj) } } -private class DefinitionWrappedInVitality(definition: ObjectDefinition) - extends ObjectDefinition(definition.ObjectId) +private class DefinitionWrappedInVitality(private val entity: PlanetSideGameObject) + extends ObjectDefinition(entity.Definition.ObjectId) with VitalityDefinition { - innateDamage = definition match { + private val internalDefinition = entity.Definition + + innateDamage = internalDefinition match { case v: VitalityDefinition if v.innateDamage.nonEmpty => v.innateDamage.get case p: ProjectileDefinition => p case _ => GlobalDefinitions.no_projectile } - Name = { definition.Name } + + Name = internalDefinition.Name + DefaultHealth = 1 //just cuz + + override def ObjectId: Int = entity match { + case p: Projectile => p.tool_def.ObjectId //projectiles point back to the weapon of origin + case _ => internalDefinition.ObjectId //what are we? + } } object DummyExplodingEntity { diff --git a/src/main/scala/net/psforever/objects/vital/etc/OicwLilBuddyReason.scala b/src/main/scala/net/psforever/objects/vital/etc/OicwLilBuddyReason.scala new file mode 100644 index 00000000..fbe2e1dc --- /dev/null +++ b/src/main/scala/net/psforever/objects/vital/etc/OicwLilBuddyReason.scala @@ -0,0 +1,26 @@ +package net.psforever.objects.vital.etc + +import net.psforever.objects.GlobalDefinitions +import net.psforever.objects.sourcing.SourceEntry +import net.psforever.objects.vital.base.{DamageReason, DamageResolution} +import net.psforever.objects.vital.prop.DamageProperties +import net.psforever.objects.vital.resolution.DamageAndResistance + +case class OicwLilBuddyReason( + entity: SourceEntry, + projectileId: Long, + damageModel: DamageAndResistance + ) extends DamageReason { + def resolution: DamageResolution.Value = DamageResolution.Explosion + + def same(test: DamageReason): Boolean = test match { + case eer: OicwLilBuddyReason => eer.projectileId == projectileId + case _ => false + } + + def adversary: Option[SourceEntry] = Some(entity) + + def source: DamageProperties = GlobalDefinitions.oicw_little_buddy + + override def attribution: Int = GlobalDefinitions.oicw.ObjectId +}