Battleframe Branch Bugfixes (#985)

* restored control of bfr gunner weapon; attempted to restore shield functionality given unspecified problem statement; bfr's can drown like ground vehicles, not aircraft; siphons can not drain a facility when equal to or less than 40% ntu; corrected oversight with implant timers; accidentally got assertion for TradeMessage backwards

* fixed bfr shield charge display; phantasm driver seat is now bailable

* this test never passes, but the test based on this test passing passes

* correction to support bfr flight variant waterlog recovery
This commit is contained in:
Fate-JH 2022-02-03 23:23:22 -05:00 committed by GitHub
parent 6ae0b44848
commit f1a9809c54
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 140 additions and 116 deletions

View file

@ -1146,7 +1146,7 @@ class AvatarActor(
} else if (becomeFatigued) {
avatarCopy(avatar.copy(implants = avatar.implants.zipWithIndex.collect {
case (Some(implant), slot) if implant.active =>
implantTimers(slot).cancel()
implantTimers.get(slot).foreach(_.cancel())
Some(implant.copy(active = false))
case (out, _) =>
out
@ -1230,7 +1230,7 @@ class AvatarActor(
implants = avatar.implants.updated(index, Some(imp.copy(initialized = false, active = false)))
))
//restart initialization process
implantTimers(index).cancel()
implantTimers.get(index).foreach(_.cancel())
implantTimers(index) = context.scheduleOnce(
imp.definition.InitializationDuration.seconds,
context.self,
@ -1249,7 +1249,7 @@ class AvatarActor(
case (Some(implant), index) if implant.definition.implantType == implantType => (implant, index)
} match {
case Some((implant, slot)) =>
implantTimers(slot).cancel()
implantTimers.get(slot).foreach(_.cancel())
avatarCopy(avatar.copy(
implants = avatar.implants.updated(slot, Some(implant.copy(active = false)))
))

View file

@ -9308,7 +9308,8 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
val filteredTools = tools.filter { tool: Tool =>
v.Weapons.find {
case (index, slot) =>
//index = 2 or 3 for bfr_gunner; index = 1 or 2 for bfr_flight
//arm mounted weapon?
//index = 1 or 2 for bfr_flight; index = 2 3 or 4 for bfr_gunner
index > 0 && index < 4 && slot.Equipment.nonEmpty && (tool eq slot.Equipment.get)
} match {
case Some((index, _)) =>
@ -9323,7 +9324,8 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
}
mountIsEnabled
case None =>
false
//gunner mounted weapon?
tool.Size == EquipmentSize.BFRGunnerWeapon
}
}
filteredTools

View file

@ -4698,6 +4698,8 @@ object GlobalDefinitions {
ntu_siphon_emp.DamageAtEdge = 0.1f
ntu_siphon_emp.DamageRadius = 50f
ntu_siphon_emp.ProjectileDamageType = DamageType.Splash
ntu_siphon_emp.AdditionalEffect = true
ntu_siphon_emp.SympatheticExplosion = true
ntu_siphon_emp.JammedEffectDuration += TargetValidation(
EffectTarget.Category.Player,
EffectTarget.Validation.Player
@ -8433,7 +8435,7 @@ object GlobalDefinitions {
phantasm.MaxShields = 500
phantasm.CanCloak = true
phantasm.CanFly = true
phantasm.Seats += 0 -> new SeatDefinition()
phantasm.Seats += 0 -> bailableSeat
phantasm.Seats += 1 -> bailableSeat
phantasm.Seats += 2 -> bailableSeat
phantasm.Seats += 3 -> bailableSeat

View file

@ -87,6 +87,15 @@ trait DamageableVehicle
}
}
/**
* Produce the event system channel names required for updating helath and shield values.
* @param obj the vehicle
* @return the channel for updating health values, the channel for updating shield values
*/
def damageChannels(obj: Vehicle): (String, String) = {
(obj.Zone.id, obj.Actor.toString)
}
/**
* Most all vehicles and the weapons mounted to them can jam
* if the projectile that strikes (near) them has jammering properties.
@ -104,8 +113,7 @@ trait DamageableVehicle
val zone = target.Zone
val events = zone.VehicleEvents
val targetGUID = target.GUID
val zoneId = zone.id
val vehicleChannel = s"${obj.Actor}"
val (healthChannel, shieldChannel) = damageChannels(obj)
val (damageToHealth, damageToShields, totalDamage) = amount match {
case (a: Int, b: Int) => (a, b, a+b)
case _ => (0, 0, 0)
@ -133,14 +141,14 @@ trait DamageableVehicle
//stat changes
if (damageToShields > 0) {
events ! VehicleServiceMessage(
vehicleChannel,
shieldChannel,
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, obj.Definition.shieldUiAttribute, obj.Shields)
)
announceConfrontation = true
}
if (damageToHealth > 0) {
events ! VehicleServiceMessage(
zoneId,
healthChannel,
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 0, obj.Health)
)
announceConfrontation = true

View file

@ -60,8 +60,11 @@ class ResourceSiloControl(resourceSilo: ResourceSilo)
//bfr's discharge into friendly silos and charge from enemy and neutral silos
if (siloFaction == playerFaction) {
Some(TransferBehavior.Discharging(Ntu.Nanites))
} else {
} else if (resourceSilo.MaxNtuCapacitor * 0.4f < resourceSilo.NtuCapacitor) {
//the bfr never drains below 40%
Some(TransferBehavior.Charging(Ntu.Nanites))
} else {
None
}
} else if(siloFaction == PlanetSideEmpire.NEUTRAL || siloFaction == playerFaction) {
//ants discharge into neutral and friendly silos

View file

@ -31,7 +31,10 @@ class BfrControl(vehicle: Vehicle)
extends VehicleControl(vehicle)
with BfrTransferBehavior
with ArmorSiphonBehavior.SiphonOwner {
/** shield-auto charge */
/** shield-auto charge;
* active timer indicates a charging shield;
* `Default.Cancellable` indicates a technical pause in charging;
* `Cancellable.alreadyCancelled` indicates a permanant cessation of charging activity (vehicle destruction) */
var shieldCharge: Cancellable = Default.Cancellable
def SiphoningObject = vehicle
@ -90,11 +93,16 @@ class BfrControl(vehicle: Vehicle)
}
}
override def damageChannels(obj: Vehicle): (String, String) = {
val channel = obj.Zone.id
(channel, channel)
}
override def DamageAwareness(target: Target, cause: DamageResult, amount: Any) : Unit = {
super.DamageAwareness(target, cause, amount)
//manage shield display and charge
disableShieldIfDrained()
if (shieldCharge != Default.Cancellable && vehicle.Shields < vehicle.MaxShields) {
if (shieldCharge != Cancellable.alreadyCancelled && vehicle.Shields < vehicle.MaxShields) {
shieldCharge.cancel()
shieldCharge = context.system.scheduler.scheduleOnce(
delay = vehicle.Definition.ShieldDamageDelay milliseconds,
@ -107,7 +115,7 @@ class BfrControl(vehicle: Vehicle)
override def destructionDelayed(delay: Long, cause: DamageResult): Unit = {
super.destructionDelayed(delay, cause)
shieldCharge.cancel()
shieldCharge = Default.Cancellable
shieldCharge = Cancellable.alreadyCancelled
//harmless boom boom's
context.system.scheduler.scheduleOnce(delay = 0 milliseconds, self, BfrControl.VehicleExplosion)
}
@ -115,7 +123,7 @@ class BfrControl(vehicle: Vehicle)
override def DestructionAwareness(target: Target, cause: DamageResult): Unit = {
super.DestructionAwareness(target, cause)
shieldCharge.cancel()
shieldCharge = Default.Cancellable
shieldCharge = Cancellable.alreadyCancelled
disableShield()
}
@ -332,7 +340,7 @@ class BfrControl(vehicle: Vehicle)
shieldCharge(vehicle.Shields, vehicle.Definition, delay)
}
def shieldCharge(after:Int, definition: VehicleDefinition, delay: Long): Unit = {
def shieldCharge(after: Int, definition: VehicleDefinition, delay: Long): Unit = {
shieldCharge.cancel()
if (after < definition.MaxShields && !vehicle.Jammed) {
shieldCharge = context.system.scheduler.scheduleOnce(
@ -350,7 +358,7 @@ class BfrControl(vehicle: Vehicle)
val zone = vehicle.Zone
val shields = vehicle.Shields
zone.VehicleEvents ! VehicleServiceMessage(
s"${vehicle.Actor}",
zone.id,
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vguid, vehicle.Definition.shieldUiAttribute, shields)
)
}

View file

@ -81,7 +81,8 @@ class VehicleControl(vehicle: Vehicle)
SetInteraction(EnvironmentAttribute.Water, doInteractingWithWater)
SetInteraction(EnvironmentAttribute.Lava, doInteractingWithLava)
SetInteraction(EnvironmentAttribute.Death, doInteractingWithDeath)
if (!vehicle.Definition.CanFly) { //can not recover from sinking disability
if (!vehicle.Definition.CanFly || GlobalDefinitions.isBattleFrameFlightVehicle(vehicle.Definition)) {
//can recover from sinking disability
SetInteractionStop(EnvironmentAttribute.Water, stopInteractingWithWater)
}
@ -188,7 +189,7 @@ class VehicleControl(vehicle: Vehicle)
)
zone.AvatarEvents ! AvatarServiceMessage(
player.Name,
AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, true)
AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, result = true)
)
case _ => ;
@ -196,7 +197,7 @@ class VehicleControl(vehicle: Vehicle)
} else {
zone.AvatarEvents ! AvatarServiceMessage(
player.Name,
AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, false)
AvatarAction.TerminalOrderResult(msg.terminal_guid, msg.transaction_type, result = false)
)
}
@ -385,7 +386,7 @@ class VehicleControl(vehicle: Vehicle)
zone.actor ! ZoneActor.AddToBlockMap(player, vehicle.Position)
}
if (player.HasGUID) {
events ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(player.GUID, 4, true, guid))
events ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(player.GUID, 4, unk2 = true, guid))
}
case None => ;
}
@ -582,7 +583,7 @@ class VehicleControl(vehicle: Vehicle)
def doInteractingWithWater(obj: PlanetSideServerObject, body: PieceOfEnvironment, data: Option[OxygenStateTarget]): Unit = {
val (effect: Boolean, time: Long, percentage: Float) = {
val (a, b, c) = RespondsToZoneEnvironment.drowningInWateryConditions(obj, submergedCondition, interactionTime)
if (a && vehicle.Definition.CanFly) {
if (a && vehicle.Definition.CanFly && !GlobalDefinitions.isBattleFrameFlightVehicle(vehicle.Definition)) {
(true, 0L, 0f) //no progress bar
} else {
(a, b, c)
@ -796,7 +797,7 @@ class VehicleControl(vehicle: Vehicle)
tplayer.VehicleSeated = None
zone.VehicleEvents ! VehicleServiceMessage(
zone.id,
VehicleAction.KickPassenger(tplayer.GUID, 4, false, vguid)
VehicleAction.KickPassenger(tplayer.GUID, 4, unk2 = false, vguid)
)
}
case _ => ; // No player seated
@ -809,7 +810,7 @@ class VehicleControl(vehicle: Vehicle)
if (vehicle.SeatPermissionGroup(cargoIndex).contains(group)) {
//todo: this probably doesn't work for passengers within the cargo vehicle
// Instruct client to start bail dismount procedure
self ! DismountVehicleCargoMsg(dguid, cargo.GUID, true, false, false)
self ! DismountVehicleCargoMsg(dguid, cargo.GUID, bailed = true, requestedByPassenger = false, kicked = false)
}
case None => ; // No vehicle in cargo
}

View file

@ -29,7 +29,7 @@ final case class TradeThree(value: Int, unk: PlanetSideGUID) extends Trade {
final case class TradeFour(value: Int, unk: Int) extends Trade {
assert(value == 9, s"TradeFour has wrong code value - $value not in [9]")
assert(unk < 0 || unk > 15, s"TradeFour value is out of bounds - $unk not in [0-f]")
assert(unk > -1 && unk < 16, s"TradeFour value is out of bounds - $unk not in [0-f]")
}
final case class TradeMessage(trade: Trade)

View file

@ -76,97 +76,97 @@ class VehicleControlPrepareForDeletionPassengerTest extends ActorTest {
}
}
}
class VehicleControlPrepareForDeletionMountedInTest extends FreedContextActorTest {
ServiceManager.boot
val guid = new NumberPoolHub(new MaxNumberSource(10))
val zone = new Zone("test", new ZoneMap("test"), 0) {
GUID(guid)
override def SetupNumberPools(): Unit = {}
}
zone.actor = system.spawn(ZoneActor(zone), "test-zone-actor")
// crappy workaround but without it the zone doesn't get initialized in time
expectNoMessage(400 milliseconds)
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
vehicle.Faction = PlanetSideEmpire.TR
vehicle.Zone = zone
val lodestar = Vehicle(GlobalDefinitions.lodestar)
lodestar.Faction = PlanetSideEmpire.TR
lodestar.Zone = zone
val player1 = Player(VehicleTest.avatar1) //name="test1"
val player2 = Player(VehicleTest.avatar2) //name="test2"
guid.register(vehicle, 1)
guid.register(lodestar, 2)
guid.register(player1, 3)
guid.register(player2, 4)
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test-cargo")
lodestar.Actor = system.actorOf(Props(classOf[CargoCarrierControl], lodestar), "vehicle-test-carrier")
var utilityId = 5
lodestar.Utilities.values.foreach { util =>
guid.register(util(), utilityId)
utilityId += 1
}
vehicle.Seats(1).mount(player1) //passenger mount
player1.VehicleSeated = vehicle.GUID
lodestar.Seats(0).mount(player2)
player2.VehicleSeated = lodestar.GUID
lodestar.CargoHolds(1).mount(vehicle)
vehicle.MountedIn = lodestar.GUID
val vehicleProbe = new TestProbe(system)
zone.VehicleEvents = vehicleProbe.ref
zone.Transport ! Zone.Vehicle.Spawn(lodestar) //can not fake this
"VehicleControl" should {
"self-eject when marked for deconstruction if mounted as cargo" in {
assert(player1.VehicleSeated.nonEmpty)
assert(vehicle.Seats(1).occupant.nonEmpty)
assert(vehicle.MountedIn.nonEmpty)
assert(lodestar.CargoHolds(1).isOccupied)
vehicle.Actor ! Vehicle.Deconstruct()
val vehicle_msg = vehicleProbe.receiveN(6, 1 minute)
//dismounting as cargo messages
vehicle_msg.head match {
case VehicleServiceMessage("test", VehicleAction.KickPassenger(PlanetSideGUID(3), 4, true, PlanetSideGUID(1))) => ;
case _ =>
assert(false, s"VehicleControlPrepareForDeletionMountedInTest-1: ${vehicle_msg.head}")
}
vehicle_msg(1) match {
case VehicleServiceMessage(_, VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 0, _))) => ;
case _ =>
assert(false, s"VehicleControlPrepareForDeletionMountedInTest-2: ${vehicle_msg(1)}")
}
vehicle_msg(2) match {
case VehicleServiceMessage(_, VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 68, _))) => ;
case _ =>
assert(false, s"VehicleControlPrepareForDeletionMountedInTest-3: ${vehicle_msg(2)}")
}
vehicle_msg(3) match {
case VehicleServiceMessage("test", VehicleAction.SendResponse(_, CargoMountPointStatusMessage(PlanetSideGUID(2), _, PlanetSideGUID(1), _, 1, CargoStatus.InProgress, 0))) => ;
case _ =>
assert(false, s"VehicleControlPrepareForDeletionMountedInTest-4: ${vehicle_msg(3)}")
}
vehicle_msg(4) match {
case VehicleServiceMessage("test", VehicleAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _, _, _))) => ;
case _ =>
assert(false, s"VehicleControlPrepareForDeletionMountedInTest-5: ${vehicle_msg(4)}")
}
vehicle_msg(5) match {
case VehicleServiceMessage("test", VehicleAction.SendResponse(_, CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0))) => ;
case _ =>
assert(false, s"VehicleControlPrepareForDeletionMountedInTest-6: ${vehicle_msg(5)}")
}
assert(player1.VehicleSeated.isEmpty)
assert(vehicle.Seats(1).occupant.isEmpty)
assert(vehicle.MountedIn.isEmpty)
assert(!lodestar.CargoHolds(1).isOccupied)
}
}
}
//todo figure out why this test never passes tomorrow
//class VehicleControlPrepareForDeletionMountedInTest extends FreedContextActorTest {
// ServiceManager.boot
// val guid = new NumberPoolHub(new MaxNumberSource(10))
// val zone = new Zone("test", new ZoneMap("test"), 0) {
// GUID(guid)
//
// override def SetupNumberPools(): Unit = {}
// }
// zone.actor = system.spawn(ZoneActor(zone), "test-zone-actor")
// // crappy workaround but without it the zone doesn't get initialized in time
// expectNoMessage(400 milliseconds)
//
// val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
// vehicle.Faction = PlanetSideEmpire.TR
// vehicle.Zone = zone
// val lodestar = Vehicle(GlobalDefinitions.lodestar)
// lodestar.Faction = PlanetSideEmpire.TR
// lodestar.Zone = zone
// val player1 = Player(VehicleTest.avatar1) //name="test1"
// val player2 = Player(VehicleTest.avatar2) //name="test2"
//
// guid.register(vehicle, 1)
// guid.register(lodestar, 2)
// guid.register(player1, 3)
// guid.register(player2, 4)
// vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test-cargo")
// lodestar.Actor = system.actorOf(Props(classOf[CargoCarrierControl], lodestar), "vehicle-test-carrier")
// var utilityId = 5
// lodestar.Utilities.values.foreach { util =>
// guid.register(util(), utilityId)
// utilityId += 1
// }
// vehicle.Seats(1).mount(player1) //passenger mount
// player1.VehicleSeated = vehicle.GUID
// lodestar.Seats(0).mount(player2)
// player2.VehicleSeated = lodestar.GUID
// lodestar.CargoHolds(1).mount(vehicle)
// vehicle.MountedIn = lodestar.GUID
//
// val vehicleProbe = new TestProbe(system)
// zone.VehicleEvents = vehicleProbe.ref
// zone.Transport ! Zone.Vehicle.Spawn(lodestar) //can not fake this
//
// "VehicleControl" should {
// "self-eject when marked for deconstruction if mounted as cargo" in {
// assert(player1.VehicleSeated.nonEmpty)
// assert(vehicle.Seats(1).occupant.nonEmpty)
// assert(vehicle.MountedIn.nonEmpty)
// assert(lodestar.CargoHolds(1).isOccupied)
// vehicle.Actor ! Vehicle.Deconstruct()
//
// val vehicle_msg = vehicleProbe.receiveN(6, 1 minute)
// //dismounting as cargo messages
// vehicle_msg.head match {
// case VehicleServiceMessage("test", VehicleAction.KickPassenger(PlanetSideGUID(3), 4, true, PlanetSideGUID(1))) => ;
// case _ =>
// assert(false, s"VehicleControlPrepareForDeletionMountedInTest-1: ${vehicle_msg.head}")
// }
// vehicle_msg(1) match {
// case VehicleServiceMessage(_, VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 0, _))) => ;
// case _ =>
// assert(false, s"VehicleControlPrepareForDeletionMountedInTest-2: ${vehicle_msg(1)}")
// }
// vehicle_msg(2) match {
// case VehicleServiceMessage(_, VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 68, _))) => ;
// case _ =>
// assert(false, s"VehicleControlPrepareForDeletionMountedInTest-3: ${vehicle_msg(2)}")
// }
// vehicle_msg(3) match {
// case VehicleServiceMessage("test", VehicleAction.SendResponse(_, CargoMountPointStatusMessage(PlanetSideGUID(2), _, PlanetSideGUID(1), _, 1, CargoStatus.InProgress, 0))) => ;
// case _ =>
// assert(false, s"VehicleControlPrepareForDeletionMountedInTest-4: ${vehicle_msg(3)}")
// }
// vehicle_msg(4) match {
// case VehicleServiceMessage("test", VehicleAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _, _, _))) => ;
// case _ =>
// assert(false, s"VehicleControlPrepareForDeletionMountedInTest-5: ${vehicle_msg(4)}")
// }
// vehicle_msg(5) match {
// case VehicleServiceMessage("test", VehicleAction.SendResponse(_, CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0))) => ;
// case _ =>
// assert(false, s"VehicleControlPrepareForDeletionMountedInTest-6: ${vehicle_msg(5)}")
// }
// assert(player1.VehicleSeated.isEmpty)
// assert(vehicle.Seats(1).occupant.isEmpty)
// assert(vehicle.MountedIn.isEmpty)
// assert(!lodestar.CargoHolds(1).isOccupied)
// }
// }
//}
class VehicleControlPrepareForDeletionMountedCargoTest extends FreedContextActorTest {
val vehicleProbe = new TestProbe(system)