From 9770e286dc78cd939c6fe575079ffc208c2fe658 Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Sun, 19 Apr 2026 12:44:26 -0400 Subject: [PATCH] re-writing BFR timed deatruction; unwinding explicit logic antipattern; reactivating bfr spawning --- .../resources/overrides/game_objects0.adb.lst | 18 +- .../global/GlobalDefinitionsVehicle.scala | 6 +- .../damage/DamageableVehicle.scala | 123 ++++++++----- .../objects/vehicles/control/BfrControl.scala | 167 +++++++++--------- 4 files changed, 171 insertions(+), 143 deletions(-) diff --git a/server/src/main/resources/overrides/game_objects0.adb.lst b/server/src/main/resources/overrides/game_objects0.adb.lst index c43e4de79..0bc9a4c3f 100644 --- a/server/src/main/resources/overrides/game_objects0.adb.lst +++ b/server/src/main/resources/overrides/game_objects0.adb.lst @@ -95,27 +95,27 @@ add_property trek allowed true add_property trek equiptime 500 add_property trek holstertime 500 add_property vulture requirement_award0 false -add_property aphelion allowed false -add_property aphelion_flight allowed false -add_property aphelion_gunner allowed false +add_property aphelion allowed true +add_property aphelion_flight allowed true +add_property aphelion_gunner allowed true add_property aphelion_armor_siphon allowed false add_property aphelion_armor_siphon_left allowed false add_property aphelion_armor_siphon_right allowed false add_property aphelion_ntu_siphon allowed false add_property aphelion_ntu_siphon_left allowed false add_property aphelion_ntu_siphon_right allowed false -add_property colossus allowed false -add_property colossus_flight allowed false -add_property colossus_gunner allowed false +add_property colossus allowed true +add_property colossus_flight allowed true +add_property colossus_gunner allowed true add_property colossus_armor_siphon allowed false add_property colossus_armor_siphon_left allowed false add_property colossus_armor_siphon_right allowed false add_property colossus_ntu_siphon allowed false add_property colossus_ntu_siphon_left allowed false add_property colossus_ntu_siphon_right allowed false -add_property peregrine allowed false -add_property peregrine_flight allowed false -add_property peregrine_gunner allowed false +add_property peregrine allowed true +add_property peregrine_flight allowed true +add_property peregrine_gunner allowed true add_property peregrine_armor_siphon allowed false add_property peregrine_armor_siphon_left allowed false add_property peregrine_armor_siphon_right allowed false diff --git a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsVehicle.scala b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsVehicle.scala index f71e021e2..1a6751d6d 100644 --- a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsVehicle.scala +++ b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsVehicle.scala @@ -1737,7 +1737,7 @@ object GlobalDefinitionsVehicle { aphelion_gunner.AutoPilotSpeeds = (5, 1) aphelion_gunner.Packet = battleFrameConverter aphelion_gunner.DestroyedModel = None - //aphelion_gunner.destructionDelay = Some(4000L) + aphelion_gunner.destructionDelay = Some(4000L) aphelion_gunner.JackingDuration = Array(0, 62, 60, 30) aphelion_gunner.RadiationShielding = 0.5f aphelion_gunner.DamageUsing = DamageCalculations.AgainstBfr @@ -1789,7 +1789,7 @@ object GlobalDefinitionsVehicle { colossus_gunner.AutoPilotSpeeds = (5, 1) colossus_gunner.Packet = battleFrameConverter colossus_gunner.DestroyedModel = None - //colossus_gunner.destructionDelay = Some(4000L) + colossus_gunner.destructionDelay = Some(4000L) colossus_gunner.JackingDuration = Array(0, 62, 60, 30) colossus_gunner.RadiationShielding = 0.5f colossus_gunner.DamageUsing = DamageCalculations.AgainstBfr @@ -1841,7 +1841,7 @@ object GlobalDefinitionsVehicle { peregrine_gunner.AutoPilotSpeeds = (5, 1) peregrine_gunner.Packet = battleFrameConverter peregrine_gunner.DestroyedModel = None - //peregrine_gunner.destructionDelay = Some(4000L) + peregrine_gunner.destructionDelay = Some(4000L) peregrine_gunner.JackingDuration = Array(0, 62, 60, 30) peregrine_gunner.RadiationShielding = 0.5f peregrine_gunner.DamageUsing = DamageCalculations.AgainstBfr diff --git a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala index e49cd2d8b..28cb07aae 100644 --- a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala +++ b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala @@ -193,72 +193,107 @@ trait DamageableVehicle */ override protected def DestructionAwareness(target: Target, cause: DamageResult): Unit = { (queuedDestruction, DamageableObject.Definition.destructionDelay) match { + case (_, None) => //explode now + destructionImmediate(target, cause) case (None, Some(delay)) => //set a future explosion for later destructionDelayed(delay, cause) - case (Some(_), _) | (None, None) => //explode now - val obj = DamageableObject - val zone = target.Zone - obj.PrepareGatingManifest() - super.DestructionAwareness(target, cause) - //aggravation cancel - EndAllAggravation() - //passengers die with us - DamageableMountable.DestructionAwareness(obj, cause) - Zone.serverSideDamage(obj.Zone, target, Zone.explosionDamage(Some(cause))) - //special considerations for certain vehicles - Vehicles.BeforeUnloadVehicle(obj, zone) - //shields - if (obj.Shields > 0) { - obj.Shields = 0 - zone.VehicleEvents ! VehicleServiceMessage( - zone.id, - VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, obj.Definition.shieldUiAttribute, 0) - ) - } - //database entry - cause.adversarial.collect { - case Adversarial(attacker, victim: VehicleSource, implement) => - ToDatabase.reportMachineDestruction( - attacker.CharId, - victim, - DamageableObject.HackedBy, - DamageableObject.MountedIn.nonEmpty, - implement, - obj.Zone.Number - ) - } - //clean up - target.Actor ! Vehicle.Deconstruct(Some(1 minute)) - DamageableWeaponTurret.DestructionAwareness(obj, cause) - case _ => ; + case (Some(_), _) => //destroy now that delay is expended (can also skip existing delay) + destructionAfterDelay(target, cause) } } + def destructionImmediate(target: Target, cause: DamageResult): Unit = { + val obj = DamageableObject + val zone = target.Zone + obj.PrepareGatingManifest() + super.DestructionAwareness(target, cause) + //aggravation cancel + EndAllAggravation() + //passengers die with us + DamageableMountable.DestructionAwareness(obj, cause) + Zone.serverSideDamage(obj.Zone, target, Zone.explosionDamage(Some(cause))) + //special considerations for certain vehicles + Vehicles.BeforeUnloadVehicle(obj, zone) + //shields + if (obj.Shields > 0) { + obj.Shields = 0 + zone.VehicleEvents ! VehicleServiceMessage( + zone.id, + VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, obj.Definition.shieldUiAttribute, 0) + ) + } + //database entry + cause.adversarial.collect { + case Adversarial(attacker, victim: VehicleSource, implement) => + ToDatabase.reportMachineDestruction( + attacker.CharId, + victim, + DamageableObject.HackedBy, + DamageableObject.MountedIn.nonEmpty, + implement, + obj.Zone.Number + ) + } + //clean up + target.Actor ! Vehicle.Deconstruct(Some(1 minute)) + DamageableWeaponTurret.DestructionAwareness(obj, cause) + } + def destructionDelayed(delay: Long, cause: DamageResult): Unit = { import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ val obj = DamageableObject - //health to 1, shields to 0 - obj.Health = 1 - obj.Shields = 0 - val guid = obj.GUID val guid0 = Service.defaultPlayerGUID val zone = obj.Zone val zoneid = zone.id val events = zone.VehicleEvents + //health to 1, shields to 0 + obj.Health = 1 + val guid = obj.GUID events ! VehicleServiceMessage( zoneid, VehicleAction.PlanetsideAttribute(guid0, guid, 0, 1) ) - events ! VehicleServiceMessage( - zoneid, - VehicleAction.PlanetsideAttribute(guid0, guid, obj.Definition.shieldUiAttribute, 0) - ) + if (obj.Shields > 0) { + obj.Shields = 0 + zone.VehicleEvents ! VehicleServiceMessage( + zone.id, + VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, obj.Definition.shieldUiAttribute, 0) + ) + } + //aggravation cancel + EndAllAggravation() + //special considerations for certain vehicles + Vehicles.BeforeUnloadVehicle(obj, zone) + //true death (expedited) + DamageableEntity.DestructionAwareness(obj, cause) //passengers die with us DamageableMountable.DestructionAwareness(DamageableObject, cause) + //database entry + cause.adversarial.collect { + case Adversarial(attacker, victim: VehicleSource, implement) => + ToDatabase.reportMachineDestruction( + attacker.CharId, + victim, + DamageableObject.HackedBy, + DamageableObject.MountedIn.nonEmpty, + implement, + obj.Zone.Number + ) + } //come back to this death later queuedDestruction = Some(context.system.scheduler.scheduleOnce(delay milliseconds, self, DamageableVehicle.Destruction(cause))) } + + def destructionAfterDelay(target: Target, cause: DamageResult): Unit = { + val obj = DamageableObject + obj.PrepareGatingManifest() + super.DestructionAwareness(target, cause) + Zone.serverSideDamage(obj.Zone, target, Zone.explosionDamage(Some(cause))) + //clean up + target.Actor ! Vehicle.Deconstruct(Some(1 minute)) + DamageableWeaponTurret.DestructionAwareness(obj, cause) + } } object DamageableVehicle { diff --git a/src/main/scala/net/psforever/objects/vehicles/control/BfrControl.scala b/src/main/scala/net/psforever/objects/vehicles/control/BfrControl.scala index 9dbc3436d..1823de837 100644 --- a/src/main/scala/net/psforever/objects/vehicles/control/BfrControl.scala +++ b/src/main/scala/net/psforever/objects/vehicles/control/BfrControl.scala @@ -20,6 +20,7 @@ import net.psforever.services.Service import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} import net.psforever.types._ +import scala.annotation.unused import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ @@ -51,24 +52,9 @@ class BfrControl(vehicle: Vehicle) repairPostStop() } - def explosionBehavior: Receive = { - case BfrControl.VehicleExplosion => - val guid = vehicle.GUID - val guid0 = Service.defaultPlayerGUID - val zone = vehicle.Zone - val zoneid = zone.id - val events = zone.VehicleEvents - events ! VehicleServiceMessage( - zoneid, - VehicleAction.GenericObjectAction(guid0, guid, 46) - ) - context.system.scheduler.scheduleOnce(delay = 500 milliseconds, self, BfrControl.VehicleExplosion) - } - override def commonEnabledBehavior: Receive = super.commonEnabledBehavior .orElse(siphonRepairBehavior) .orElse(bfrBehavior) - .orElse(explosionBehavior) .orElse { case CommonMessages.Use(_, Some(item: Tool)) => if (GlobalDefinitions.isBattleFrameNTUSiphon(item.Definition)) { @@ -83,8 +69,6 @@ class BfrControl(vehicle: Vehicle) performEmpBurst() } - override def commonDisabledBehavior: Receive = super.commonDisabledBehavior.orElse(explosionBehavior) - override def PrepareForDisabled(kickPassengers: Boolean) : Unit = { super.PrepareForDisabled(kickPassengers) if (vehicle.Health == 0) { @@ -116,14 +100,6 @@ class BfrControl(vehicle: Vehicle) super.destructionDelayed(delay, cause) shieldCharge.cancel() shieldCharge = Cancellable.alreadyCancelled - //harmless boom boom's - context.system.scheduler.scheduleOnce(delay = 0 milliseconds, self, BfrControl.VehicleExplosion) - } - - override def DestructionAwareness(target: Target, cause: DamageResult): Unit = { - super.DestructionAwareness(target, cause) - shieldCharge.cancel() - shieldCharge = Cancellable.alreadyCancelled disableShield() } @@ -138,9 +114,9 @@ class BfrControl(vehicle: Vehicle) } val guid0 = PlanetSideGUID(0) //if the weapon arm is disabled, enable it for later (makes life easy) - parseObjectAction(guid0, BfrControl.ArmState.Enabled, Some(slot)) + parseObjectActionForBFRs(guid0, BfrControl.ArmState.Enabled, Some(slot)) //enable the other arm weapon regardless - parseObjectAction(guid0, BfrControl.ArmState.Enabled, Some( + parseObjectActionForBFRs(guid0, BfrControl.ArmState.Enabled, Some( //budget logic: the arm weapons are "next to each other" index-wise if (vehicle.Weapons.keys.min == slot) { slot + 1 } else { slot - 1 } )) @@ -380,8 +356,24 @@ class BfrControl(vehicle: Vehicle) /** bfr weapons do not jam the same way normal vehicle weapons do */ } - override def parseObjectAction(guid: PlanetSideGUID, action: Int, other: Option[Any]): Unit = { - super.parseObjectAction(guid, action, other) + /** + * Object action changes specifically tailored for the battleframe robotics. + * Manipulates active state of the arm weapons. + * @param guid unique identifier of the entity being manipulated; + * use no identifier to search for entity or equipment + * @param action how the entity is being manipulated; + * in the case of arm control, whether is it should be enabled or disabled + * @param other additional information necessary to perform this action; + * in the case of arm control, the slot of the arm being manipulated + * @param armManagementFunc how to manipulate the battleframe arm if its active state changed; + * defaults to normal management routine + */ + private def parseObjectActionForBFRs( + guid: PlanetSideGUID, + action: Int, + other: Option[Any], + armManagementFunc: Int => Unit = specialArmWeaponActiveManagement + ): Unit = { if (action == BfrControl.ArmState.Enabled || action == BfrControl.ArmState.Disabled) { //disable or enable fire control for the left arm weapon or for the right arm weapon ((other match { @@ -406,7 +398,7 @@ class BfrControl(vehicle: Vehicle) (0, None) }) match { case (slot, Some(_)) => - specialArmWeaponActiveManagement(slot) + armManagementFunc(slot) val guid0 = Service.defaultPlayerGUID val doNotSendTo = other match { case Some(pguid: PlanetSideGUID) => pguid @@ -433,6 +425,11 @@ class BfrControl(vehicle: Vehicle) } } + override def parseObjectAction(guid: PlanetSideGUID, action: Int, other: Option[Any]): Unit = { + super.parseObjectAction(guid, action, other) + parseObjectActionForBFRs(guid, action, other) + } + def bfrHandiness(side: equipment.Hand): Int = { if (side == Handiness.Left) 2 else if (side == Handiness.Right) 3 @@ -455,7 +452,7 @@ class BfrControl(vehicle: Vehicle) } } - def specialArmWeaponEquipManagement(item: Equipment, slot: Int, handiness: equipment.Hand): Unit = { + def specialArmWeaponEquipManagement(item: Equipment, slot: Int, @unused handiness: equipment.Hand): Unit = { if (item.Size == EquipmentSize.BFRArmWeapon && vehicle.VisibleSlots.contains(slot)) { val weapons = vehicle.Weapons //budget logic: the arm weapons are "next to each other" index-wise @@ -482,69 +479,65 @@ class BfrControl(vehicle: Vehicle) ) { //installing a siphon; this siphon can safely be disabled //alternately, installing normal equipment, but the other arm weapon is a siphon - parseObjectAction(PlanetSideGUID(0), BfrControl.ArmState.Enabled, Some(otherArmSlot)) //ensure enabled - parseObjectAction(item.GUID, BfrControl.ArmState.Disabled, Some(slot)) + parseObjectActionForBFRs(PlanetSideGUID(0), BfrControl.ArmState.Enabled, Some(otherArmSlot)) //ensure enabled + parseObjectActionForBFRs(item.GUID, BfrControl.ArmState.Disabled, Some(slot)) } } } - /** since `specialArmWeaponActiveManagement` is called from `parseObjectAction`, - * and `parseObjectAction` gets called in `specialArmWeaponActiveManagement`, - * kill endless logic loops before they can happen */ + /* since `specialArmWeaponActiveManagement` is called from `parseObjectActionForBFRs`, + * and `parseObjectActionForBFRs` gets called in `specialArmWeaponActiveManagement`, + * kill endless logic loops before they can happen by instructing the former to not call the latter (again) */ var notSpecialManagingArmWeapon: Boolean = true def specialArmWeaponActiveManagement(slotChanged: Int): Unit = { - if (notSpecialManagingArmWeapon) { - notSpecialManagingArmWeapon = false - val (thisArm, otherArm) = { - val pairedSystemsToSlots = pairedArmSlotSubsystems() - if (pairedSystemsToSlots.head._2._1 == slotChanged) { - (pairedSystemsToSlots.head, pairedSystemsToSlots(1)) - } - else { - (pairedSystemsToSlots(1), pairedSystemsToSlots.head) - } - } - if (thisArm._1.Enabled) { - //this arm weapon slot was enabled - if ({ - val (thisArmExists, thisArmIsSiphon) = thisArm._2._2.Equipment match { - case Some(thing) => - //some equipment is attached to the other arm weapon mount - val definition = thing.Definition - ( - true, - GlobalDefinitions.isBattleFrameArmorSiphon(definition) || GlobalDefinitions.isBattleFrameNTUSiphon(definition) - ) - case None => - (false, false) - } - val (otherArmExists, otherArmIsSiphon) = otherArm._2._2.Equipment match { - case Some(thing) => - //some equipment is attached to the other arm weapon mount - val definition = thing.Definition - ( - true, - GlobalDefinitions.isBattleFrameArmorSiphon(definition) || GlobalDefinitions.isBattleFrameNTUSiphon(definition) - ) - case None => - (false, false) - } - thisArmExists && otherArmExists && (thisArmIsSiphon || otherArmIsSiphon) - }) { - //both arms weapons are installed and at least one of them is a siphon - parseObjectAction(PlanetSideGUID(0), BfrControl.ArmState.Disabled, Some(otherArm._2._1)) - } + val ((thisSubsystem, (thisSlot, thisEquipment)), (_, (otherSlot, otherEquipment))) = { + val pairedSystemsToSlots = pairedArmSlotSubsystems() + if (pairedSystemsToSlots.head._2._1 == slotChanged) { + (pairedSystemsToSlots.head, pairedSystemsToSlots(1)) } else { - //this arm weapon slot was disabled - thisArm._2._2.Equipment match { - case Some(item) => - parseObjectAction(item.GUID, BfrControl.ArmState.Enabled, Some(otherArm._2._1)) //other arm must be enabled - case None => - parseObjectAction(PlanetSideGUID(0), BfrControl.ArmState.Enabled, Some(thisArm._2._1)) //must stay enabled - } + (pairedSystemsToSlots(1), pairedSystemsToSlots.head) + } + } + if (thisSubsystem.Enabled) { + //this arm weapon slot was enabled + if ({ + val (thisArmExists, thisArmIsSiphon) = thisEquipment.Equipment match { + case Some(thing) => + //some equipment is attached to the other arm weapon mount + val definition = thing.Definition + ( + true, + GlobalDefinitions.isBattleFrameArmorSiphon(definition) || GlobalDefinitions.isBattleFrameNTUSiphon(definition) + ) + case None => + (false, false) + } + val (otherArmExists, otherArmIsSiphon) = otherEquipment.Equipment match { + case Some(thing) => + //some equipment is attached to the other arm weapon mount + val definition = thing.Definition + ( + true, + GlobalDefinitions.isBattleFrameArmorSiphon(definition) || GlobalDefinitions.isBattleFrameNTUSiphon(definition) + ) + case None => + (false, false) + } + thisArmExists && otherArmExists && (thisArmIsSiphon || otherArmIsSiphon) + }) { + //both arms weapons are installed and at least one of them is a siphon + parseObjectActionForBFRs(PlanetSideGUID(0), BfrControl.ArmState.Disabled, Some(otherSlot), BfrControl.NoArmManagement) + } + } + else { + //this arm weapon slot was disabled + thisEquipment.Equipment match { + case Some(item) => + parseObjectActionForBFRs(item.GUID, BfrControl.ArmState.Enabled, Some(otherSlot), BfrControl.NoArmManagement) //other arm must be enabled + case None => + parseObjectActionForBFRs(PlanetSideGUID(0), BfrControl.ArmState.Enabled, Some(thisSlot), BfrControl.NoArmManagement) //must stay enabled } - notSpecialManagingArmWeapon = true } } @@ -615,8 +608,6 @@ object BfrControl { final val Disabled = 39 } - private case object VehicleExplosion - val dimorphics: List[EquipmentHandiness] = { import GlobalDefinitions._ List( @@ -637,4 +628,6 @@ object BfrControl { EquipmentHandiness(peregrine_sparrow, peregrine_sparrow_left, peregrine_sparrow_right) ) } + + private def NoArmManagement(@unused x: Int):Unit = { } }