Collisions (#932)

* pattern for applying damage to player avatar and player-controlled vehicle collisions

* pattern for applying damage to targets due to collisions, falling damage and crashing damage individually; fields to support these calculations are provided

* modifiers to translate 'small step velocity' to real game velocity, as reported by the HUD; corrections for velocity; corrections for velocity in other packets

* fall damage calculations moved to function

* basic two-body collisions between GUID-identified game entities and a ward against too many collisions in a short amount of time

* bailing mechanics

* vssm for non-driven vehicles

* comment about vehicle state message field

* comments and minor refactoring for current collision damage calc; tank_traps modifier; potential fix for blockmap indexing issue

* fixed cargo/carrier vehicle ops

* corrections to initialization of ce construction items; adjustments to handling of modifiers for collision damage

* modifier change, protection against flight speed and spectator crashes; submerged status is once again known only to the actor

* appeasing the automated tests

* hopefully paced collisions better; re-did how Infantry collisions are calculated, incorporating mass and exo-suit data; kill feed reporting should be better

* adjusted damage values again, focusing on the lesser of or middling results; collision killfeed attribution attempt

* kicking offers bail protection; lowered the artificial modifier for one kind of collision damage calculation

* correction to the local reference map functions

* fixed tests; attempt to zero fall damage distance based on velocity; attempt to block mine damage when spectating
This commit is contained in:
Fate-JH 2021-10-05 09:59:49 -04:00 committed by GitHub
parent 0001ef6ce5
commit 93a544c07c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
56 changed files with 2024 additions and 1029 deletions

View file

@ -44,6 +44,7 @@ import net.psforever.objects.vehicles.Utility.InternalTelepad
import net.psforever.objects.vehicles._
import net.psforever.objects.vital._
import net.psforever.objects.vital.base._
import net.psforever.objects.vital.collision.{CollisionReason, CollisionWithReason}
import net.psforever.objects.vital.etc.ExplodingEntityReason
import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.objects.vital.projectile.ProjectileReason
@ -206,7 +207,8 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
var nextSpawnPoint: Option[SpawnPoint] = None
var setupAvatarFunc: () => Unit = AvatarCreate
var setCurrentAvatarFunc: Player => Unit = SetCurrentAvatarNormally
var persist: () => Unit = NoPersistence
var persistFunc: () => Unit = NoPersistence
var persist: () => Unit = UpdatePersistenceOnly
var specialItemSlotGuid: Option[PlanetSideGUID] =
None // If a special item (e.g. LLU) has been attached to the player the GUID should be stored here, or cleared when dropped, since the drop hotkey doesn't send the GUID of the object to be dropped.
@ -275,6 +277,10 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
var setAvatar: Boolean = false
var turnCounterFunc: PlanetSideGUID => Unit = TurnCounterDuringInterim
var waypointCooldown: Long = 0L
var heightLast: Float = 0f
var heightTrend: Boolean = false //up = true, down = false
var heightHistory: Float = 0f
val collisionHistory: mutable.HashMap[ActorRef, Long] = mutable.HashMap()
var clientKeepAlive: Cancellable = Default.Cancellable
var progressBarUpdate: Cancellable = Default.Cancellable
@ -1172,6 +1178,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case NewPlayerLoaded(tplayer) =>
//new zone
log.info(s"${tplayer.Name} has spawned into ${session.zone.id}")
persist = UpdatePersistenceAndRefs
tplayer.avatar = avatar
session = session.copy(player = tplayer)
avatarActor ! AvatarActor.CreateImplants()
@ -1379,7 +1386,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case PlayerToken.LoginInfo(name, Zone.Nowhere, _) =>
log.info(s"LoginInfo: player $name is considered a new character")
//TODO poll the database for saved zone and coordinates?
persist = UpdatePersistence(sender())
persistFunc = UpdatePersistence(sender())
deadState = DeadState.RespawnTime
session = session.copy(player = new Player(avatar))
@ -1395,7 +1402,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case PlayerToken.LoginInfo(playerName, inZone, pos) =>
log.info(s"LoginInfo: player $playerName is already logged in zone ${inZone.id}; rejoining that character")
persist = UpdatePersistence(sender())
persistFunc = UpdatePersistence(sender())
//tell the old WorldSessionActor to kill itself by using its own subscriptions against itself
inZone.AvatarEvents ! AvatarServiceMessage(playerName, AvatarAction.TeardownConnection())
//find and reload previous player
@ -1477,18 +1484,36 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
/**
* Update this player avatar for persistence.
* @param persistRef reference to the persistence monitor
* Set to `persist` initially.
*/
def UpdatePersistence(persistRef: ActorRef)(): Unit = {
persistRef ! AccountPersistenceService.Update(player.Name, continent, player.Position)
def UpdatePersistenceOnly(): Unit = {
persistFunc()
}
/**
* Update this player avatar for persistence.
* Set to `persist` when (new) player is loaded.
*/
def UpdatePersistenceAndRefs(): Unit = {
persistFunc()
updateOldRefsMap()
}
/**
* Do not update this player avatar for persistence.
* Set to `persistFunc` initially.
*/
def NoPersistence(): Unit = {}
/**
* Update this player avatar for persistence.
* Set this to `persistFunc` when persistence is ready.
* @param persistRef reference to the persistence monitor
*/
def UpdatePersistence(persistRef: ActorRef)(): Unit = {
persistRef ! AccountPersistenceService.Update(player.Name, continent, player.Position)
}
/**
* A zoning message was received.
* That doesn't matter.
@ -1585,11 +1610,11 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
CancelAllProximityUnits()
//droppod action
val droppod = Vehicle(GlobalDefinitions.droppod)
droppod.GUID = PlanetSideGUID(0) //droppod is not registered, we must jury-rig this
droppod.Faction = player.Faction
droppod.Position = spawnPosition.xy + Vector3.z(1024)
droppod.Orientation = Vector3.z(180) //you always seems to land looking south; don't know why
droppod.Seats(0).mount(player)
droppod.GUID = PlanetSideGUID(0) //droppod is not registered, we must jury-rig this
droppod.Invalidate() //now, we must short-circuit the jury-rig
interstellarFerry = Some(droppod) //leverage vehicle gating
player.Position = droppod.Position
@ -1642,7 +1667,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
*/
def HandleAvatarServiceResponse(toChannel: String, guid: PlanetSideGUID, reply: AvatarResponse.Response): Unit = {
val tplayer_guid =
if (player.HasGUID) player.GUID
if (player != null && player.HasGUID) player.GUID
else PlanetSideGUID(0)
reply match {
case AvatarResponse.TeardownConnection() =>
@ -2525,12 +2550,12 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
obj.Actor ! Vehicle.Deconstruct()
case Mountable.CanDismount(obj: Vehicle, seat_num, _) =>
log.info(
s"${tplayer.Name} dismounts a ${obj.Definition.asInstanceOf[ObjectDefinition].Name} from seat #$seat_num"
)
val player_guid: PlanetSideGUID = tplayer.GUID
if (player_guid == player.GUID) {
//disembarking self
log.info(
s"${tplayer.Name} dismounts a ${obj.Definition.asInstanceOf[ObjectDefinition].Name} from seat #$seat_num"
)
ConditionalDriverVehicleControl(obj)
UnaccessContainer(obj)
DismountAction(tplayer, obj, seat_num)
@ -2742,12 +2767,14 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
//but always seems to return 4 if user is kicked by mount permissions changing
sendResponse(DismountVehicleMsg(guid, BailType.Kicked, wasKickedByDriver))
if (tplayer_guid == guid) {
log.info(s"{${player.Name} has been kicked from ${player.Sex.possessive} ride!")
continent.GUID(vehicle_guid) match {
val typeOfRide = continent.GUID(vehicle_guid) match {
case Some(obj: Vehicle) =>
UnaccessContainer(obj)
case _ => ;
s"the ${obj.Definition.Name}'s seat by ${obj.OwnerName.getOrElse("the pilot")}"
case _ =>
s"${player.Sex.possessive} ride"
}
log.info(s"${player.Name} has been kicked from $typeOfRide!")
}
case VehicleResponse.InventoryState2(obj_guid, parent_guid, value) =>
@ -2969,7 +2996,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
cargo: Vehicle,
mountPoint: Int
): (ObjectAttachMessage, CargoMountPointStatusMessage) = {
val msgs @ (attachMessage, mountPointStatusMessage) = CargoBehavior.CargoMountMessages(carrier, cargo, mountPoint)
val msgs @ (attachMessage, mountPointStatusMessage) = CarrierBehavior.CargoMountMessages(carrier, cargo, mountPoint)
CargoMountMessagesForUs(attachMessage, mountPointStatusMessage)
msgs
}
@ -3336,8 +3363,8 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
(continent.GUID(cargo_guid), continent.GUID(carrier_guid)) match {
case (Some(cargo: Vehicle), Some(carrier: Vehicle)) =>
carrier.CargoHolds.find({ case (_, hold) => !hold.isOccupied }) match {
case Some((mountPoint, _)) => //try begin the mount process
cargo.Actor ! CargoBehavior.CheckCargoMounting(carrier_guid, mountPoint, 0)
case Some((mountPoint, _)) =>
cargo.Actor ! CargoBehavior.StartCargoMounting(carrier_guid, mountPoint)
case _ =>
log.warn(
s"MountVehicleCargoMsg: ${player.Name} trying to load cargo into a ${carrier.Definition.Name} which oes not have a cargo hold"
@ -3350,17 +3377,11 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case _ => ;
}
case msg @ DismountVehicleCargoMsg(player_guid, cargo_guid, bailed, requestedByPassenger, kicked) =>
case msg @ DismountVehicleCargoMsg(_, cargo_guid, bailed, _, kicked) =>
log.debug(s"DismountVehicleCargoMsg: $msg")
//when kicked by carrier driver, player_guid will be PlanetSideGUID(0)
//when exiting of the cargo vehicle driver's own accord, player_guid will be the cargo vehicle driver
continent.GUID(cargo_guid) match {
case Some(cargo: Vehicle) if !requestedByPassenger =>
continent.GUID(cargo.MountedIn) match {
case Some(carrier: Vehicle) =>
CargoBehavior.HandleVehicleCargoDismount(continent, cargo_guid, bailed, requestedByPassenger, kicked)
case _ => ;
}
case Some(cargo: Vehicle) =>
cargo.Actor ! CargoBehavior.StartCargoDismounting(bailed || kicked)
case _ => ;
}
@ -3633,7 +3654,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case vehicle if vehicle.CargoHolds.nonEmpty =>
vehicle.CargoHolds.collect {
case (_index, hold: Cargo) if hold.isOccupied =>
CargoBehavior.CargoMountBehaviorForAll(
CarrierBehavior.CargoMountBehaviorForAll(
vehicle,
hold.occupant.get,
_index
@ -3779,6 +3800,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
if (isMovingPlus) {
CancelZoningProcessWithDescriptiveReason("cancel_motion")
}
fallHeightTracker(pos.z)
// if (is_crouching && !player.Crouching) {
// //dev stuff goes here
// }
@ -3909,10 +3931,10 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
//we're driving the vehicle
persist()
turnCounterFunc(player.GUID)
fallHeightTracker(pos.z)
if (obj.MountedIn.isEmpty) {
updateBlockMap(obj, continent, pos)
}
val seat = obj.Seats(0)
player.Position = pos //convenient
if (obj.WeaponControlledFromSeat(0).isEmpty) {
player.Orientation = Vector3.z(ang.z) //convenient
@ -3969,10 +3991,38 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
KickedByAdministration()
}
case msg @ VehicleSubStateMessage(vehicle_guid, player_guid, vehicle_pos, vehicle_ang, vel, unk1, unk2) =>
log.debug(
s"VehicleSubState: $vehicle_guid, ${player.Name}_guid, $vehicle_pos, $vehicle_ang, $vel, $unk1, $unk2"
)
case msg @ VehicleSubStateMessage(vehicle_guid, _, pos, ang, vel, unk1, unk2) =>
//log.info(s"msg")
ValidObject(vehicle_guid) match {
case Some(obj: Vehicle) =>
obj.Position = pos
obj.Orientation = ang
obj.Velocity = vel
updateBlockMap(obj, continent, pos)
obj.zoneInteractions()
continent.VehicleEvents ! VehicleServiceMessage(
continent.id,
VehicleAction.VehicleState(
player.GUID,
vehicle_guid,
unk1,
pos,
ang,
obj.Velocity,
obj.Flying,
0,
0,
15,
false,
obj.Cloaked
)
)
case _ =>
log.warn(
s"VehicleSubState: ${player.Name} should not be dispatching this kind of packet for vehicle #${vehicle_guid.guid}"
)
}
case msg @ ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, seq, end, target_guid) =>
val index = projectile_guid.guid - Projectile.baseUID
@ -5447,7 +5497,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case Some(obj: Mountable) =>
obj.PassengerInSeat(player) match {
case Some(seat_num) =>
obj.Actor ! Mountable.TryDismount(player, seat_num)
obj.Actor ! Mountable.TryDismount(player, seat_num, bailType)
if (interstellarFerry.isDefined) {
//short-circuit the temporary channel for transferring between zones, the player is no longer doing that
//see above in VehicleResponse.TransferPassenger case
@ -5489,7 +5539,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case (Some(obj: Mountable), Some(tplayer: Player)) =>
obj.PassengerInSeat(tplayer) match {
case Some(seat_num) =>
obj.Actor ! Mountable.TryDismount(tplayer, seat_num)
obj.Actor ! Mountable.TryDismount(tplayer, seat_num, bailType)
case None =>
dismountWarning(
s"DismountVehicleMsg: can not find where other player ${player.Name}_guid is seated in mountable $obj_guid"
@ -5552,8 +5602,111 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.Waypoint(request, wtype, unk, info))
}
case msg @ GenericCollisionMsg(u1, p, t, php, thp, pv, tv, ppos, tpos, u2, u3, u4) =>
log.info(s"${player.Name} would be in intense and excruciating pain right now if collision worked")
case msg @ GenericCollisionMsg(ctype, p, php, ppos, pv, t, thp, tpos, tv, u1, u2, u3) =>
//log.info(s"$msg")
val fallHeight = {
if (pv.z * pv.z >= (pv.x * pv.x + pv.y * pv.y) * 0.5f) {
if (heightTrend) {
val fall = heightLast - heightHistory
heightHistory = heightLast
fall
}
else {
val fall = heightHistory - heightLast
heightLast = heightHistory
fall
}
} else {
0f
}
}
val (target1, target2, bailProtectStatus, velocity) = (ctype, ValidObject(p)) match {
case (CollisionIs.OfInfantry, out @ Some(user: Player))
if user == player =>
val bailStatus = session.flying || player.spectator || session.speed > 1f || player.BailProtection
player.BailProtection = false
val v = if (player.avatar.implants.exists {
case Some(implant) => implant.definition.implantType == ImplantType.Surge && implant.active
case _ => false
}) {
Vector3.Zero
} else {
pv
}
(out, None, bailStatus, v)
case (CollisionIs.OfGroundVehicle, out @ Some(v: Vehicle))
if v.Seats(0).occupant.contains(player) =>
val bailStatus = v.BailProtection
v.BailProtection = false
(out, ValidObject(t), bailStatus, pv)
case (CollisionIs.OfAircraft, out @ Some(v: Vehicle))
if v.Definition.CanFly && v.Seats(0).occupant.contains(player) =>
(out, ValidObject(t), false, pv)
case _ =>
(None, None, false, Vector3.Zero)
}
val curr = System.currentTimeMillis()
(target1, t, target2) match {
case (None, _, _) => ;
case (Some(us: PlanetSideServerObject with Vitality with FactionAffinity), PlanetSideGUID(0), _) =>
if (collisionHistory.get(us.Actor) match {
case Some(lastCollision) if curr - lastCollision <= 1000L =>
false
case _ =>
collisionHistory.put(us.Actor, curr)
true
}) {
if (!bailProtectStatus) {
HandleDealingDamage(
us,
DamageInteraction(
SourceEntry(us),
CollisionReason(velocity, fallHeight, us.DamageModel),
ppos
)
)
}
}
case (
Some(us: PlanetSideServerObject with Vitality with FactionAffinity), _,
Some(victim: PlanetSideServerObject with Vitality with FactionAffinity)
) =>
if (collisionHistory.get(victim.Actor) match {
case Some(lastCollision) if curr - lastCollision <= 1000L =>
false
case _ =>
collisionHistory.put(victim.Actor, curr)
true
}) {
val usSource = SourceEntry(us)
val victimSource = SourceEntry(victim)
//we take damage from the collision
if (!bailProtectStatus) {
HandleDealingDamage(
us,
DamageInteraction(
usSource,
CollisionWithReason(CollisionReason(velocity - tv, fallHeight, us.DamageModel), victimSource),
ppos
)
)
}
//get dealt damage from our own collision (no protection)
collisionHistory.put(us.Actor, curr)
HandleDealingDamage(
victim,
DamageInteraction(
victimSource,
CollisionWithReason(CollisionReason(tv - velocity, 0, victim.DamageModel), usSource),
tpos
)
)
}
case _ => ;
}
case msg @ BugReportMessage(
version_major,
@ -6760,6 +6913,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
progressBarValue = None
lastTerminalOrderFulfillment = true
kitToBeUsed = None
collisionHistory.clear()
accessedContainer match {
case Some(v: Vehicle) =>
val vguid = v.GUID
@ -6863,7 +7017,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
)
carrierInfo match {
case (Some(carrier), Some((index, _))) =>
CargoBehavior.CargoMountBehaviorForOthers(carrier, vehicle, index, player.GUID)
CarrierBehavior.CargoMountBehaviorForOthers(carrier, vehicle, index, player.GUID)
case _ =>
vehicle.MountedIn = None
}
@ -7725,10 +7879,15 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
def DismountAction(tplayer: Player, obj: PlanetSideGameObject with Mountable, seatNum: Int): Unit = {
val player_guid: PlanetSideGUID = tplayer.GUID
keepAliveFunc = NormalKeepAlive
sendResponse(DismountVehicleMsg(player_guid, BailType.Normal, wasKickedByDriver = false))
val bailType = if (tplayer.BailProtection) {
BailType.Bailed
} else {
BailType.Normal
}
sendResponse(DismountVehicleMsg(player_guid, bailType, wasKickedByDriver = false))
continent.VehicleEvents ! VehicleServiceMessage(
continent.id,
VehicleAction.DismountVehicle(player_guid, BailType.Normal, false)
VehicleAction.DismountVehicle(player_guid, bailType, false)
)
}
@ -7748,18 +7907,41 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
val func = data.calculate()
target match {
case obj: Player if obj.CanDamage && obj.Actor != Default.Actor =>
log.info(s"${player.Name} is attacking ${obj.Name}")
if (obj.CharId != player.CharId) {
log.info(s"${player.Name} is attacking ${obj.Name}")
} else {
log.info(s"${player.Name} hurt ${player.Sex.pronounObject}self")
}
// auto kick players damaging spectators
if (obj.spectator && obj != player) {
AdministrativeKick(player)
} else {
obj.Actor ! Vitality.Damage(func)
}
case obj: Vehicle if obj.CanDamage =>
log.info(s"${player.Name} is attacking ${obj.OwnerName.getOrElse("someone")}'s ${obj.Definition.Name}")
val name = player.Name
val ownerName = obj.OwnerName.getOrElse("someone")
if (ownerName.equals(name)) {
log.info(s"$name is damaging ${player.Sex.possessive} own ${obj.Definition.Name}")
} else {
log.info(s"$name is attacking $ownerName's ${obj.Definition.Name}")
}
obj.Actor ! Vitality.Damage(func)
case obj: Amenity if obj.CanDamage => obj.Actor ! Vitality.Damage(func)
case obj: Deployable if obj.CanDamage => obj.Actor ! Vitality.Damage(func)
case obj: Amenity if obj.CanDamage =>
obj.Actor ! Vitality.Damage(func)
case obj: Deployable if obj.CanDamage =>
val name = player.Name
val ownerName = obj.OwnerName.getOrElse("someone")
if (ownerName.equals(name)) {
log.info(s"$name is damaging ${player.Sex.possessive} own ${obj.Definition.Name}")
} else {
log.info(s"$name is attacking $ownerName's ${obj.Definition.Name}")
}
obj.Actor ! Vitality.Damage(func)
case _ => ;
}
}
@ -8197,7 +8379,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
log.warn(
s"LoadZoneInVehicleAsDriver: ${player.Name} must eject cargo in hold $index; vehicle is missing driver"
)
CargoBehavior.HandleVehicleCargoDismount(cargo.GUID, cargo, vehicle.GUID, vehicle, false, false, true)
cargo.Actor ! CargoBehavior.StartCargoDismounting(bailed = false)
case entry =>
val cargo = vehicle.CargoHolds(entry.mount).occupant.get
continent.VehicleEvents ! VehicleServiceMessage(
@ -9139,26 +9321,28 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
var oldRefsMap: mutable.HashMap[PlanetSideGUID, String] = new mutable.HashMap[PlanetSideGUID, String]()
def updateOldRefsMap(): Unit = {
oldRefsMap.addAll(
(continent.GUID(player.VehicleSeated) match {
case Some(v: Vehicle) =>
v.Weapons.toList.collect {
case (_, slot: EquipmentSlot) if slot.Equipment.nonEmpty => updateOldRefsMap(slot.Equipment.get)
}.flatten ++
updateOldRefsMap(v.Inventory)
case _ =>
Map.empty[PlanetSideGUID, String]
}) ++
(accessedContainer match {
case Some(cont) => updateOldRefsMap(cont.Inventory)
case None => Map.empty[PlanetSideGUID, String]
}) ++
player.Holsters().toList.collect {
case slot if slot.Equipment.nonEmpty => updateOldRefsMap(slot.Equipment.get)
}.flatten ++
updateOldRefsMap(player.Inventory) ++
updateOldRefsMap(player.avatar.locker.Inventory)
)
if(player.HasGUID) {
oldRefsMap.addAll(
(continent.GUID(player.VehicleSeated) match {
case Some(v : Vehicle) =>
v.Weapons.toList.collect {
case (_, slot : EquipmentSlot) if slot.Equipment.nonEmpty => updateOldRefsMap(slot.Equipment.get)
}.flatten ++
updateOldRefsMap(v.Inventory)
case _ =>
Map.empty[PlanetSideGUID, String]
}) ++
(accessedContainer match {
case Some(cont) => updateOldRefsMap(cont.Inventory)
case None => Map.empty[PlanetSideGUID, String]
}) ++
player.Holsters().toList.collect {
case slot if slot.Equipment.nonEmpty => updateOldRefsMap(slot.Equipment.get)
}.flatten ++
updateOldRefsMap(player.Inventory) ++
updateOldRefsMap(player.avatar.locker.Inventory)
)
}
}
def updateOldRefsMap(inventory: net.psforever.objects.inventory.GridInventory): IterableOnce[(PlanetSideGUID, String)] = {
@ -9179,6 +9363,21 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
}
}
def fallHeightTracker(zHeight: Float): Unit = {
if ((heightTrend && heightLast - zHeight >= 0.5f) ||
(!heightTrend && zHeight - heightLast >= 0.5f)) {
heightTrend = !heightTrend
// if (heightTrend) {
// GetMountableAndSeat(None, player, continent) match {
// case (Some(v: Vehicle), _) => v.BailProtection = false
// case _ => player.BailProtection = false
// }
// }
heightHistory = zHeight
}
heightLast = zHeight
}
def failWithError(error: String) = {
log.error(error)
middlewareActor ! MiddlewareActor.Teardown()

View file

@ -137,7 +137,7 @@ class ZoneActor(context: ActorContext[ZoneActor.Command], zone: Zone)
zone.blockMap.addTo(target, toPosition)
case UpdateBlockMap(target, toPosition) =>
zone.blockMap.move(target, toPosition)
target.updateBlockMapEntry(toPosition)
case RemoveFromBlockMap(target) =>
zone.blockMap.removeFrom(target)

View file

@ -146,19 +146,30 @@ object Deployables {
* If the default ammunition mode for the `ConstructionTool` is not supported by the given certifications,
* find a suitable ammunition mode and switch to it internally.
* No special complaint is raised if the `ConstructionItem` itself is completely unsupported.
* The search function will explore every ammo option for every fire mode option
* and will stop when it finds either a valid option or when arrives back at the original fire mode.
* @param certs the certification baseline being compared against
* @param obj the `ConstructionItem` entity
* @return `true`, if the ammunition mode of the item has been changed;
* @return `true`, if the firemode and ammunition mode of the item is valid;
* `false`, otherwise
*/
def initializeConstructionAmmoMode(
certs: Set[Certification],
obj: ConstructionItem
): Boolean = {
def initializeConstructionItem(
certs: Set[Certification],
obj: ConstructionItem
): Boolean = {
val initialFireModeIndex = obj.FireModeIndex
if (!Deployables.constructionItemPermissionComparison(certs, obj.ModePermissions)) {
Deployables.performConstructionItemAmmoChange(certs, obj, obj.AmmoTypeIndex)
while (!Deployables.constructionItemPermissionComparison(certs, obj.ModePermissions) &&
!Deployables.performConstructionItemAmmoChange(certs, obj, obj.AmmoTypeIndex) &&
{
obj.NextFireMode
initialFireModeIndex != obj.FireModeIndex
}) {
/* change in fire mode occurs in conditional */
}
Deployables.constructionItemPermissionComparison(certs, obj.ModePermissions)
} else {
false
true
}
}

View file

@ -31,22 +31,28 @@ import net.psforever.objects.vital.damage._
import net.psforever.objects.vital.etc.ExplodingRadialDegrade
import net.psforever.objects.vital.projectile._
import net.psforever.objects.vital.prop.DamageWithPosition
import net.psforever.objects.vital.{ComplexDeployableResolutions, MaxResolutions, SimpleResolutions}
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.vital.collision.TrapCollisionDamageMultiplier
import scala.collection.mutable
import scala.concurrent.duration._
object GlobalDefinitions {
// Characters
/*
characters
*/
val avatar = new AvatarDefinition(121)
avatar.MaxHealth = 100
avatar.Damageable = true
avatar.DrownAtMaxDepth = true
avatar.MaxDepth = 1.609375f //Male, standing, not MAX
avatar.UnderwaterLifespan(suffocation = 60000L, recovery = 10000L)
avatar.collision.xy = CollisionXYData(Array((0.1f, 0), (0.2f, 5), (0.50f, 15), (0.75f, 20), (1f, 30))) //not in the ADB
avatar.collision.z = CollisionZData(Array((0.1f, 0), (5f, 1), (10f, 3), (20f, 5), (35f, 7), (50f, 10), (75f, 40), (100f, 100))) //not in the ADB
avatar.maxForwardSpeed = 27f //not in the ADB; running speed
/*
exo-suits
*/
@ -1704,6 +1710,8 @@ object GlobalDefinitions {
Standard.ResistanceDirectHit = 4
Standard.ResistanceSplash = 15
Standard.ResistanceAggravated = 8
Standard.collision.forceFactor = 1.5f
Standard.collision.massFactor = 2f
Agile.Name = "lite_armor"
Agile.Descriptor = "agile"
@ -1717,6 +1725,8 @@ object GlobalDefinitions {
Agile.ResistanceDirectHit = 6
Agile.ResistanceSplash = 25
Agile.ResistanceAggravated = 10
Agile.collision.forceFactor = 1.5f
Agile.collision.massFactor = 2f
Reinforced.Name = "med_armor"
Reinforced.Descriptor = "reinforced"
@ -1732,6 +1742,8 @@ object GlobalDefinitions {
Reinforced.ResistanceDirectHit = 10
Reinforced.ResistanceSplash = 35
Reinforced.ResistanceAggravated = 12
Reinforced.collision.forceFactor = 2f
Reinforced.collision.massFactor = 3f
Infiltration.Name = "infiltration_suit"
Infiltration.Permissions = List(Certification.InfiltrationSuit)
@ -1752,6 +1764,8 @@ object GlobalDefinitions {
max.ResistanceDirectHit = 6
max.ResistanceSplash = 35
max.ResistanceAggravated = 10
max.collision.forceFactor = 4f
max.collision.massFactor = 10f
max.DamageUsing = DamageCalculations.AgainstMaxSuit
max.Model = MaxResolutions.calculate
}
@ -5652,10 +5666,17 @@ object GlobalDefinitions {
* Initialize `VehicleDefinition` globals.
*/
private def init_vehicles(): Unit = {
init_ground_vehicles()
init_flight_vehicles()
}
/**
* Initialize land-based `VehicleDefinition` globals.
*/
private def init_ground_vehicles(): Unit = {
val atvForm = GeometryForm.representByCylinder(radius = 1.1797f, height = 1.1875f) _
val delivererForm = GeometryForm.representByCylinder(radius = 2.46095f, height = 2.40626f) _ //TODO hexahedron
val apcForm = GeometryForm.representByCylinder(radius = 4.6211f, height = 3.90626f) _ //TODO hexahedron
val liberatorForm = GeometryForm.representByCylinder(radius = 3.74615f, height = 2.51563f) _
val bailableSeat = new SeatDefinition() {
bailable = true
@ -5694,6 +5715,11 @@ object GlobalDefinitions {
fury.MaxDepth = 1.3f
fury.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
fury.Geometry = atvForm
fury.collision.avatarCollisionDamageMax = 35
fury.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 5), (0.5f, 20), (0.75f, 40), (1f, 60)))
fury.collision.z = CollisionZData(Array((8f, 1), (24f, 35), (40f, 100), (48f, 175), (52f, 350)))
fury.maxForwardSpeed = 90f
fury.mass = 32.1f
quadassault.Name = "quadassault" // Basilisk
quadassault.MaxHealth = 650
@ -5725,6 +5751,11 @@ object GlobalDefinitions {
quadassault.MaxDepth = 1.3f
quadassault.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
quadassault.Geometry = atvForm
quadassault.collision.avatarCollisionDamageMax = 35
quadassault.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 5), (0.5f, 20), (0.75f, 40), (1f, 60)))
quadassault.collision.z = CollisionZData(Array((8f, 1), (24f, 35), (40f, 100), (48f, 175), (52f, 350)))
quadassault.maxForwardSpeed = 90f
quadassault.mass = 32.1f
quadstealth.Name = "quadstealth" // Wraith
quadstealth.MaxHealth = 650
@ -5756,6 +5787,11 @@ object GlobalDefinitions {
quadstealth.MaxDepth = 1.25f
quadstealth.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
quadstealth.Geometry = atvForm
quadstealth.collision.avatarCollisionDamageMax = 35
quadstealth.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 5), (0.5f, 20), (0.75f, 40), (1f, 60)))
quadstealth.collision.z = CollisionZData(Array((8f, 1), (24f, 35), (40f, 100), (48f, 175), (52f, 350)))
quadstealth.maxForwardSpeed = 90f
quadstealth.mass = 32.1f
two_man_assault_buggy.Name = "two_man_assault_buggy" // Harasser
two_man_assault_buggy.MaxHealth = 1250
@ -5788,6 +5824,11 @@ object GlobalDefinitions {
two_man_assault_buggy.MaxDepth = 1.5f
two_man_assault_buggy.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
two_man_assault_buggy.Geometry = GeometryForm.representByCylinder(radius = 2.10545f, height = 1.59376f)
two_man_assault_buggy.collision.avatarCollisionDamageMax = 75
two_man_assault_buggy.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 5), (0.5f, 20), (0.75f, 40), (1f, 60)))
two_man_assault_buggy.collision.z = CollisionZData(Array((7f, 1), (21f, 50), (35f, 150), (42f, 300), (45.5f, 600)))
two_man_assault_buggy.maxForwardSpeed = 85f
two_man_assault_buggy.mass = 52.4f
skyguard.Name = "skyguard"
skyguard.MaxHealth = 1000
@ -5821,6 +5862,11 @@ object GlobalDefinitions {
skyguard.MaxDepth = 1.5f
skyguard.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
skyguard.Geometry = GeometryForm.representByCylinder(radius = 1.8867f, height = 1.4375f)
skyguard.collision.avatarCollisionDamageMax = 100
skyguard.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 5), (0.5f, 20), (0.75f, 40), (1f, 60)))
skyguard.collision.z = CollisionZData(Array((7f, 1), (21f, 50), (35f, 150), (42f, 300), (45.4f, 600)))
skyguard.maxForwardSpeed = 90f
skyguard.mass = 78.9f
threemanheavybuggy.Name = "threemanheavybuggy" // Marauder
threemanheavybuggy.MaxHealth = 1700
@ -5858,6 +5904,11 @@ object GlobalDefinitions {
threemanheavybuggy.MaxDepth = 1.83f
threemanheavybuggy.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
threemanheavybuggy.Geometry = GeometryForm.representByCylinder(radius = 2.1953f, height = 2.03125f)
threemanheavybuggy.collision.avatarCollisionDamageMax = 100
threemanheavybuggy.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 15), (0.5f, 30), (0.75f, 60), (1f, 80)))
threemanheavybuggy.collision.z = CollisionZData(Array((6f, 1), (18f, 50), (30f, 150), (36f, 300), (39f, 900)))
threemanheavybuggy.maxForwardSpeed = 80f
threemanheavybuggy.mass = 96.3f
twomanheavybuggy.Name = "twomanheavybuggy" // Enforcer
twomanheavybuggy.MaxHealth = 1800
@ -5891,6 +5942,11 @@ object GlobalDefinitions {
twomanheavybuggy.MaxDepth = 1.95f
twomanheavybuggy.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
twomanheavybuggy.Geometry = GeometryForm.representByCylinder(radius = 2.60935f, height = 1.79688f)
twomanheavybuggy.collision.avatarCollisionDamageMax = 100
twomanheavybuggy.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 12), (0.5f, 30), (0.75f, 55), (1f, 80)))
twomanheavybuggy.collision.z = CollisionZData(Array((6f, 1), (18f, 50), (30f, 150), (36f, 300), (39f, 900)))
twomanheavybuggy.maxForwardSpeed = 80f
twomanheavybuggy.mass = 83.2f
twomanhoverbuggy.Name = "twomanhoverbuggy" // Thresher
twomanhoverbuggy.MaxHealth = 1600
@ -5926,6 +5982,11 @@ object GlobalDefinitions {
recovery = 5000L
) //but the thresher hovers over water, so ...?
twomanhoverbuggy.Geometry = GeometryForm.representByCylinder(radius = 2.1875f, height = 2.01563f)
twomanhoverbuggy.collision.avatarCollisionDamageMax = 125
twomanhoverbuggy.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 13), (0.5f, 35), (0.75f, 65), (1f, 90)))
twomanhoverbuggy.collision.z = CollisionZData(Array((6f, 1), (18f, 50), (30f, 150), (36f, 300), (39f, 900)))
twomanhoverbuggy.maxForwardSpeed = 85f
twomanhoverbuggy.mass = 55.5f
mediumtransport.Name = "mediumtransport" // Deliverer
mediumtransport.MaxHealth = 2500
@ -5969,6 +6030,11 @@ object GlobalDefinitions {
mediumtransport.MaxDepth = 1.2f
mediumtransport.UnderwaterLifespan(suffocation = -1, recovery = -1)
mediumtransport.Geometry = delivererForm
mediumtransport.collision.avatarCollisionDamageMax = 120
mediumtransport.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 35), (0.5f, 60), (0.75f, 110), (1f, 175)))
mediumtransport.collision.z = CollisionZData(Array((5f, 1), (15f, 50), (25f, 200), (30f, 750), (32.5f, 2000)))
mediumtransport.maxForwardSpeed = 70f
mediumtransport.mass = 108.5f
battlewagon.Name = "battlewagon" // Raider
battlewagon.MaxHealth = 2500
@ -6015,6 +6081,11 @@ object GlobalDefinitions {
battlewagon.MaxDepth = 1.2f
battlewagon.UnderwaterLifespan(suffocation = -1, recovery = -1)
battlewagon.Geometry = delivererForm
battlewagon.collision.avatarCollisionDamageMax = 120
battlewagon.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 35), (0.5f, 60), (0.75f, 110), (1f, 175))) //inherited from mediumtransport
battlewagon.collision.z = CollisionZData(Array((5f, 1), (15f, 50), (25f, 200), (30f, 750), (32.5f, 2000))) //inherited from mediumtransport
battlewagon.maxForwardSpeed = 65f
battlewagon.mass = 108.5f
thunderer.Name = "thunderer"
thunderer.MaxHealth = 2500
@ -6058,6 +6129,11 @@ object GlobalDefinitions {
thunderer.MaxDepth = 1.2f
thunderer.UnderwaterLifespan(suffocation = -1, recovery = -1)
thunderer.Geometry = delivererForm
thunderer.collision.avatarCollisionDamageMax = 120
thunderer.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 35), (0.5f, 60), (0.75f, 110), (1f, 175)))
thunderer.collision.z = CollisionZData(Array((5f, 1), (15f, 50), (25f, 200), (30f, 750), (32.5f, 2000)))
thunderer.maxForwardSpeed = 65f
thunderer.mass = 108.5f
aurora.Name = "aurora"
aurora.MaxHealth = 2500
@ -6101,6 +6177,11 @@ object GlobalDefinitions {
aurora.MaxDepth = 1.2f
aurora.UnderwaterLifespan(suffocation = -1, recovery = -1)
aurora.Geometry = delivererForm
aurora.collision.avatarCollisionDamageMax = 120
aurora.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 35), (0.5f, 60), (0.75f, 110), (1f, 175)))
aurora.collision.z = CollisionZData(Array((5f, 1), (15f, 50), (25f, 200), (30f, 750), (32.5f, 2000)))
aurora.maxForwardSpeed = 65f
aurora.mass = 108.5f
apc_tr.Name = "apc_tr" // Juggernaut
apc_tr.MaxHealth = 6000
@ -6164,6 +6245,11 @@ object GlobalDefinitions {
apc_tr.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
apc_tr.Geometry = apcForm
apc_tr.MaxCapacitor = 300
apc_tr.collision.avatarCollisionDamageMax = 300
apc_tr.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 10), (0.5f, 40), (0.75f, 70), (1f, 110)))
apc_tr.collision.z = CollisionZData(Array((2f, 1), (6f, 50), (10f, 300), (12f, 1000), (13f, 3000)))
apc_tr.maxForwardSpeed = 60f
apc_tr.mass = 128.4f
apc_nc.Name = "apc_nc" // Vindicator
apc_nc.MaxHealth = 6000
@ -6227,6 +6313,11 @@ object GlobalDefinitions {
apc_nc.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
apc_nc.Geometry = apcForm
apc_nc.MaxCapacitor = 300
apc_nc.collision.avatarCollisionDamageMax = 300
apc_nc.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 10), (0.5f, 40), (0.75f, 70), (1f, 110)))
apc_nc.collision.z = CollisionZData(Array((2f, 1), (6f, 50), (10f, 300), (12f, 1000), (13f, 3000)))
apc_nc.maxForwardSpeed = 60f
apc_nc.mass = 128.4f
apc_vs.Name = "apc_vs" // Leviathan
apc_vs.MaxHealth = 6000
@ -6290,6 +6381,11 @@ object GlobalDefinitions {
apc_vs.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
apc_vs.Geometry = apcForm
apc_vs.MaxCapacitor = 300
apc_vs.collision.avatarCollisionDamageMax = 300
apc_vs.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 10), (0.5f, 40), (0.75f, 70), (1f, 110)))
apc_vs.collision.z = CollisionZData(Array((2f, 1), (6f, 50), (10f, 300), (12f, 1000), (13f, 3000)))
apc_vs.maxForwardSpeed = 60f
apc_vs.mass = 128.4f
lightning.Name = "lightning"
lightning.MaxHealth = 2000
@ -6324,6 +6420,11 @@ object GlobalDefinitions {
lightning.MaxDepth = 1.38f
lightning.UnderwaterLifespan(suffocation = 12000L, recovery = 6000L)
lightning.Geometry = GeometryForm.representByCylinder(radius = 2.5078f, height = 1.79688f)
lightning.collision.avatarCollisionDamageMax = 150
lightning.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 10), (0.5f, 25), (0.75f, 50), (1f, 80)))
lightning.collision.z = CollisionZData(Array((6f, 1), (18f, 50), (30f, 150), (36f, 300), (39f, 750)))
lightning.maxForwardSpeed = 74f
lightning.mass = 100.2f
prowler.Name = "prowler"
prowler.MaxHealth = 4800
@ -6363,6 +6464,11 @@ object GlobalDefinitions {
prowler.MaxDepth = 3
prowler.UnderwaterLifespan(suffocation = 12000L, recovery = 6000L)
prowler.Geometry = GeometryForm.representByCylinder(radius = 3.461f, height = 3.48438f)
prowler.collision.avatarCollisionDamageMax = 300
prowler.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 15), (0.5f, 40), (0.75f, 75), (1f, 100)))
prowler.collision.z = CollisionZData(Array((5f, 1), (15f, 50), (25f, 250), (30f, 600), (32.5f, 1500)))
prowler.maxForwardSpeed = 57f
prowler.mass = 510.5f
vanguard.Name = "vanguard"
vanguard.MaxHealth = 5400
@ -6398,6 +6504,11 @@ object GlobalDefinitions {
vanguard.MaxDepth = 2.7f
vanguard.UnderwaterLifespan(suffocation = 12000L, recovery = 6000L)
vanguard.Geometry = GeometryForm.representByCylinder(radius = 3.8554f, height = 2.60938f)
vanguard.collision.avatarCollisionDamageMax = 300
vanguard.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 5), (0.5f, 20), (0.75f, 40), (1f, 60)))
vanguard.collision.z = CollisionZData(Array((5f, 1), (15f, 50), (25f, 100), (30f, 250), (32.5f, 600)))
vanguard.maxForwardSpeed = 60f
vanguard.mass = 460.4f
magrider.Name = "magrider"
magrider.MaxHealth = 4200
@ -6435,6 +6546,11 @@ object GlobalDefinitions {
magrider.MaxDepth = 2
magrider.UnderwaterLifespan(suffocation = 45000L, recovery = 5000L) //but the magrider hovers over water, so ...?
magrider.Geometry = GeometryForm.representByCylinder(radius = 3.3008f, height = 3.26562f)
magrider.collision.avatarCollisionDamageMax = 225
magrider.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 35), (0.5f, 70), (0.75f, 90), (1f, 120)))
magrider.collision.z = CollisionZData(Array((5f, 1), (15f, 50), (25f, 250), (30f, 600), (32.5f, 1500)))
magrider.maxForwardSpeed = 65f
magrider.mass = 75.3f
val utilityConverter = new UtilityVehicleConverter
ant.Name = "ant"
@ -6470,6 +6586,11 @@ object GlobalDefinitions {
ant.MaxDepth = 2
ant.UnderwaterLifespan(suffocation = 12000L, recovery = 6000L)
ant.Geometry = GeometryForm.representByCylinder(radius = 2.16795f, height = 2.09376f) //TODO hexahedron
ant.collision.avatarCollisionDamageMax = 50
ant.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 10), (0.5f, 30), (0.75f, 50), (1f, 70)))
ant.collision.z = CollisionZData(Array((2f, 1), (6f, 50), (10f, 250), (12f, 500), (13f, 750)))
ant.maxForwardSpeed = 65f
ant.mass = 80.5f
ams.Name = "ams"
ams.MaxHealth = 3000
@ -6508,6 +6629,11 @@ object GlobalDefinitions {
ams.MaxDepth = 3
ams.UnderwaterLifespan(suffocation = 5000L, recovery = 5000L)
ams.Geometry = GeometryForm.representByCylinder(radius = 3.0117f, height = 3.39062f) //TODO hexahedron
ams.collision.avatarCollisionDamageMax = 250
ams.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 10), (0.5f, 40), (0.75f, 60), (1f, 100)))
ams.collision.z = CollisionZData(Array((2f, 1), (6f, 50), (10f, 250), (12f, 805), (13f, 3000)))
ams.maxForwardSpeed = 70f
ams.mass = 136.8f
val variantConverter = new VariantVehicleConverter
router.Name = "router"
@ -6543,8 +6669,13 @@ object GlobalDefinitions {
}
router.DrownAtMaxDepth = true
router.MaxDepth = 2
router.UnderwaterLifespan(suffocation = 45000L, recovery = 5000L) //but the router hovers over water, so ...?
router.UnderwaterLifespan(suffocation = 45000L, recovery = 5000L) //but the router hovers over water, so ...?
router.Geometry = GeometryForm.representByCylinder(radius = 3.64845f, height = 3.51563f) //TODO hexahedron
router.collision.avatarCollisionDamageMax = 150 //it has to bonk you on the head when it falls?
router.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 13), (0.5f, 35), (0.75f, 65), (1f, 90)))
router.collision.z = CollisionZData(Array((6f, 1), (18f, 50), (30f, 150), (36f, 350), (39f, 900)))
router.maxForwardSpeed = 60f
router.mass = 60f
switchblade.Name = "switchblade"
switchblade.MaxHealth = 1750
@ -6585,6 +6716,11 @@ object GlobalDefinitions {
recovery = 5000L
) //but the switchblade hovers over water, so ...?
switchblade.Geometry = GeometryForm.representByCylinder(radius = 2.4335f, height = 2.73438f)
switchblade.collision.avatarCollisionDamageMax = 35
switchblade.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 13), (0.5f, 35), (0.75f, 65), (1f, 90)))
switchblade.collision.z = CollisionZData(Array((6f, 1), (18f, 50), (30f, 150), (36f, 350), (39f, 800)))
switchblade.maxForwardSpeed = 80f
switchblade.mass = 63.9f
flail.Name = "flail"
flail.MaxHealth = 2400
@ -6621,6 +6757,22 @@ object GlobalDefinitions {
flail.MaxDepth = 2
flail.UnderwaterLifespan(suffocation = 45000L, recovery = 5000L) //but the flail hovers over water, so ...?
flail.Geometry = GeometryForm.representByCylinder(radius = 2.1875f, height = 2.21875f)
flail.collision.avatarCollisionDamageMax = 175
flail.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 12), (0.5f, 35), (0.75f, 65), (1f, 90)))
flail.collision.z = CollisionZData(Array((6f, 1), (18f, 50), (30f, 150), (36f, 350), (39f, 900)))
flail.maxForwardSpeed = 55f
flail.mass = 73.5f
}
/**
* Initialize flight `VehicleDefinition` globals.
*/
private def init_flight_vehicles(): Unit = {
val liberatorForm = GeometryForm.representByCylinder(radius = 3.74615f, height = 2.51563f) _
val bailableSeat = new SeatDefinition() {
bailable = true
}
val variantConverter = new VariantVehicleConverter
mosquito.Name = "mosquito"
mosquito.MaxHealth = 665
@ -6654,6 +6806,11 @@ object GlobalDefinitions {
mosquito.DrownAtMaxDepth = true
mosquito.MaxDepth = 2 //flying vehicles will automatically disable
mosquito.Geometry = GeometryForm.representByCylinder(radius = 2.72108f, height = 2.5f)
mosquito.collision.avatarCollisionDamageMax = 50
mosquito.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 50), (0.5f, 100), (0.75f, 150), (1f, 200)))
mosquito.collision.z = CollisionZData(Array((3f, 1), (9f, 25), (15f, 50), (18f, 75), (19.5f, 100)))
mosquito.maxForwardSpeed = 120f
mosquito.mass = 53.6f
lightgunship.Name = "lightgunship" // Reaver
lightgunship.MaxHealth = 855 // Temporary - Correct Reaver Health from pre-"Coder Madness 2" Event
@ -6688,6 +6845,11 @@ object GlobalDefinitions {
lightgunship.DrownAtMaxDepth = true
lightgunship.MaxDepth = 2 //flying vehicles will automatically disable
lightgunship.Geometry = GeometryForm.representByCylinder(radius = 2.375f, height = 1.98438f)
lightgunship.collision.avatarCollisionDamageMax = 750
lightgunship.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 60), (0.5f, 120), (0.75f, 180), (1f, 250)))
lightgunship.collision.z = CollisionZData(Array((3f, 1), (9f, 30), (15f, 60), (18f, 90), (19.5f, 125)))
lightgunship.maxForwardSpeed = 104f
lightgunship.mass = 51.1f
wasp.Name = "wasp"
wasp.MaxHealth = 515
@ -6721,6 +6883,11 @@ object GlobalDefinitions {
wasp.DrownAtMaxDepth = true
wasp.MaxDepth = 2 //flying vehicles will automatically disable
wasp.Geometry = GeometryForm.representByCylinder(radius = 2.88675f, height = 2.5f)
wasp.collision.avatarCollisionDamageMax = 50 //mosquito numbers
wasp.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 50), (0.5f, 100), (0.75f, 150), (1f, 200))) //mosquito numbers
wasp.collision.z = CollisionZData(Array((3f, 1), (9f, 25), (15f, 50), (18f, 75), (19.5f, 100))) //mosquito numbers
wasp.maxForwardSpeed = 120f
wasp.mass = 53.6f
liberator.Name = "liberator"
liberator.MaxHealth = 2500
@ -6729,7 +6896,7 @@ object GlobalDefinitions {
liberator.RepairIfDestroyed = false
liberator.MaxShields = 500
liberator.CanFly = true
liberator.Seats += 0 -> new SeatDefinition()
liberator.Seats += 0 -> bailableSeat //new SeatDefinition()
liberator.Seats += 1 -> bailableSeat
liberator.Seats += 2 -> bailableSeat
liberator.controlledWeapons += 0 -> 3
@ -6763,6 +6930,11 @@ object GlobalDefinitions {
liberator.DrownAtMaxDepth = true
liberator.MaxDepth = 2 //flying vehicles will automatically disable
liberator.Geometry = liberatorForm
liberator.collision.avatarCollisionDamageMax = 100
liberator.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 60), (0.5f, 120), (0.75f, 180), (1f, 250)))
liberator.collision.z = CollisionZData(Array((3f, 1), (9f, 30), (15f, 60), (18f, 90), (19.5f, 125)))
liberator.maxForwardSpeed = 90f
liberator.mass = 82f
vulture.Name = "vulture"
vulture.MaxHealth = 2500
@ -6771,7 +6943,7 @@ object GlobalDefinitions {
vulture.RepairIfDestroyed = false
vulture.MaxShields = 500
vulture.CanFly = true
vulture.Seats += 0 -> new SeatDefinition()
vulture.Seats += 0 -> bailableSeat //new SeatDefinition()
vulture.Seats += 1 -> bailableSeat
vulture.Seats += 2 -> bailableSeat
vulture.controlledWeapons += 0 -> 3
@ -6806,6 +6978,11 @@ object GlobalDefinitions {
vulture.DrownAtMaxDepth = true
vulture.MaxDepth = 2 //flying vehicles will automatically disable
vulture.Geometry = liberatorForm
vulture.collision.avatarCollisionDamageMax = 100
vulture.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 60), (0.5f, 120), (0.75f, 180), (1f, 250)))
vulture.collision.z = CollisionZData(Array((3f, 1), (9f, 30), (15f, 60), (18f, 90), (19.5f, 125)))
vulture.maxForwardSpeed = 97f
vulture.mass = 82f
dropship.Name = "dropship" // Galaxy
dropship.MaxHealth = 5000
@ -6815,7 +6992,7 @@ object GlobalDefinitions {
dropship.RepairIfDestroyed = false
dropship.MaxShields = 1000
dropship.CanFly = true
dropship.Seats += 0 -> new SeatDefinition()
dropship.Seats += 0 -> bailableSeat //new SeatDefinition()
dropship.Seats += 1 -> bailableSeat
dropship.Seats += 2 -> bailableSeat
dropship.Seats += 3 -> bailableSeat
@ -6876,6 +7053,11 @@ object GlobalDefinitions {
dropship.DrownAtMaxDepth = true
dropship.MaxDepth = 2
dropship.Geometry = GeometryForm.representByCylinder(radius = 10.52202f, height = 6.23438f)
dropship.collision.avatarCollisionDamageMax = 300
dropship.collision.xy = CollisionXYData(Array((0.1f, 5), (0.25f, 125), (0.5f, 250), (0.75f, 500), (1f, 1000)))
dropship.collision.z = CollisionZData(Array((3f, 5), (9f, 125), (15f, 250), (18f, 500), (19.5f, 1000)))
dropship.maxForwardSpeed = 80f
dropship.mass = 133f
galaxy_gunship.Name = "galaxy_gunship"
galaxy_gunship.MaxHealth = 6000
@ -6885,7 +7067,7 @@ object GlobalDefinitions {
galaxy_gunship.RepairIfDestroyed = false
galaxy_gunship.MaxShields = 1200
galaxy_gunship.CanFly = true
galaxy_gunship.Seats += 0 -> new SeatDefinition()
galaxy_gunship.Seats += 0 -> bailableSeat //new SeatDefinition()
galaxy_gunship.Seats += 1 -> bailableSeat
galaxy_gunship.Seats += 2 -> bailableSeat
galaxy_gunship.Seats += 3 -> bailableSeat
@ -6930,6 +7112,11 @@ object GlobalDefinitions {
galaxy_gunship.DrownAtMaxDepth = true
galaxy_gunship.MaxDepth = 2
galaxy_gunship.Geometry = GeometryForm.representByCylinder(radius = 9.2382f, height = 5.01562f)
galaxy_gunship.collision.avatarCollisionDamageMax = 300
galaxy_gunship.collision.xy = CollisionXYData(Array((0.1f, 5), (0.25f, 125), (0.5f, 250), (0.75f, 500), (1f, 1000)))
galaxy_gunship.collision.z = CollisionZData(Array((3f, 5), (9f, 125), (15f, 250), (18f, 500), (19.5f, 1000)))
galaxy_gunship.maxForwardSpeed = 85f
galaxy_gunship.mass = 133f
lodestar.Name = "lodestar"
lodestar.MaxHealth = 5000
@ -6939,7 +7126,7 @@ object GlobalDefinitions {
lodestar.RepairIfDestroyed = false
lodestar.MaxShields = 1000
lodestar.CanFly = true
lodestar.Seats += 0 -> new SeatDefinition()
lodestar.Seats += 0 -> bailableSeat
lodestar.MountPoints += 1 -> MountInfo(0)
lodestar.MountPoints += 2 -> MountInfo(1)
lodestar.Cargo += 1 -> new CargoDefinition()
@ -6972,6 +7159,9 @@ object GlobalDefinitions {
lodestar.DrownAtMaxDepth = true
lodestar.MaxDepth = 2
lodestar.Geometry = GeometryForm.representByCylinder(radius = 7.8671f, height = 6.79688f) //TODO hexahedron
lodestar.collision.z = CollisionZData(Array((3f, 5), (9f, 125), (15f, 250), (18f, 500), (19.5f, 1000)))
lodestar.maxForwardSpeed = 80f
lodestar.mass = 128.2f
phantasm.Name = "phantasm"
phantasm.MaxHealth = 2500
@ -7011,6 +7201,11 @@ object GlobalDefinitions {
phantasm.DrownAtMaxDepth = true
phantasm.MaxDepth = 2
phantasm.Geometry = GeometryForm.representByCylinder(radius = 5.2618f, height = 3f)
phantasm.collision.avatarCollisionDamageMax = 100
phantasm.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 60), (0.5f, 120), (0.75f, 180), (1f, 250)))
phantasm.collision.z = CollisionZData(Array((3f, 1), (9f, 30), (15f, 60), (18f, 90), (19.5f, 125)))
phantasm.maxForwardSpeed = 140f
phantasm.mass = 100f
droppod.Name = "droppod"
droppod.MaxHealth = 20000
@ -7027,6 +7222,7 @@ object GlobalDefinitions {
droppod.DestroyedModel = None //the adb calls out a droppod; the cyclic nature of this confounds me
droppod.DamageUsing = DamageCalculations.AgainstAircraft
droppod.DrownAtMaxDepth = false
droppod.mass = 2500f
orbital_shuttle.Name = "orbital_shuttle"
orbital_shuttle.MaxHealth = 20000
@ -7059,6 +7255,7 @@ object GlobalDefinitions {
orbital_shuttle.DestroyedModel = None
orbital_shuttle.DamageUsing = DamageCalculations.AgainstNothing
orbital_shuttle.DrownAtMaxDepth = false
orbital_shuttle.mass = 25000f
}
/**
@ -7185,6 +7382,9 @@ object GlobalDefinitions {
Modifiers = ExplodingRadialDegrade
}
spitfire_turret.Geometry = smallTurret
spitfire_turret.collision.xy = CollisionXYData(Array((0.01f, 10), (0.02f, 40), (0.03f, 60), (0.04f, 80), (0.05f, 100)))
spitfire_turret.collision.z = CollisionZData(Array((4f, 10), (4.25f, 40), (4.5f, 60), (4.75f, 80), (5f, 100)))
spitfire_turret.mass = 5f
spitfire_cloaked.Name = "spitfire_cloaked"
spitfire_cloaked.Descriptor = "CloakingSpitfires"
@ -7209,6 +7409,9 @@ object GlobalDefinitions {
Modifiers = ExplodingRadialDegrade
}
spitfire_cloaked.Geometry = smallTurret
spitfire_cloaked.collision.xy = CollisionXYData(Array((0.01f, 10), (0.02f, 40), (0.03f, 60), (0.04f, 80), (0.05f, 100)))
spitfire_cloaked.collision.z = CollisionZData(Array((4f, 10), (4.25f, 40), (4.5f, 60), (4.75f, 80), (5f, 100)))
spitfire_cloaked.mass = 5f
spitfire_aa.Name = "spitfire_aa"
spitfire_aa.Descriptor = "FlakSpitfires"
@ -7233,6 +7436,9 @@ object GlobalDefinitions {
Modifiers = ExplodingRadialDegrade
}
spitfire_aa.Geometry = smallTurret
spitfire_aa.collision.xy = CollisionXYData(Array((0.01f, 10), (0.02f, 40), (0.03f, 60), (0.04f, 80), (0.05f, 100)))
spitfire_aa.collision.z = CollisionZData(Array((4f, 10), (4.25f, 40), (4.5f, 60), (4.75f, 80), (5f, 100)))
spitfire_aa.mass = 5f
motionalarmsensor.Name = "motionalarmsensor"
motionalarmsensor.Descriptor = "MotionSensors"
@ -7273,6 +7479,10 @@ object GlobalDefinitions {
Modifiers = ExplodingRadialDegrade
}
tank_traps.Geometry = GeometryForm.representByCylinder(radius = 2.89680997f, height = 3.57812f)
tank_traps.collision.xy = CollisionXYData(Array((0.01f, 5), (0.02f, 10), (0.03f, 15), (0.04f, 20), (0.05f, 25)))
tank_traps.collision.z = CollisionZData(Array((4f, 10), (4.25f, 40), (4.5f, 60), (4.75f, 80), (5f, 100)))
tank_traps.Modifiers = TrapCollisionDamageMultiplier(5f) //10f
tank_traps.mass = 600f
val fieldTurretConverter = new FieldTurretConverter
portable_manned_turret.Name = "portable_manned_turret"
@ -7303,6 +7513,9 @@ object GlobalDefinitions {
Modifiers = ExplodingRadialDegrade
}
portable_manned_turret.Geometry = largeTurret
portable_manned_turret.collision.xy = CollisionXYData(Array((0.01f, 10), (0.02f, 40), (0.03f, 60), (0.04f, 80), (0.05f, 100)))
portable_manned_turret.collision.z = CollisionZData(Array((4f, 10), (4.25f, 40), (4.5f, 60), (4.75f, 80), (5f, 100)))
portable_manned_turret.mass = 100f
portable_manned_turret_nc.Name = "portable_manned_turret_nc"
portable_manned_turret_nc.Descriptor = "FieldTurrets"
@ -7332,6 +7545,9 @@ object GlobalDefinitions {
Modifiers = ExplodingRadialDegrade
}
portable_manned_turret_nc.Geometry = largeTurret
portable_manned_turret_nc.collision.xy = CollisionXYData(Array((0.01f, 10), (0.02f, 40), (0.03f, 60), (0.04f, 80), (0.05f, 100)))
portable_manned_turret_nc.collision.z = CollisionZData(Array((4f, 10), (4.25f, 40), (4.5f, 60), (4.75f, 80), (5f, 100)))
portable_manned_turret_nc.mass = 100f
portable_manned_turret_tr.Name = "portable_manned_turret_tr"
portable_manned_turret_tr.Descriptor = "FieldTurrets"
@ -7361,6 +7577,9 @@ object GlobalDefinitions {
Modifiers = ExplodingRadialDegrade
}
portable_manned_turret_tr.Geometry = largeTurret
portable_manned_turret_tr.collision.xy = CollisionXYData(Array((0.01f, 10), (0.02f, 40), (0.03f, 60), (0.04f, 80), (0.05f, 100)))
portable_manned_turret_tr.collision.z = CollisionZData(Array((4f, 10), (4.25f, 40), (4.5f, 60), (4.75f, 80), (5f, 100)))
portable_manned_turret_tr.mass = 100f
portable_manned_turret_vs.Name = "portable_manned_turret_vs"
portable_manned_turret_vs.Descriptor = "FieldTurrets"
@ -7390,6 +7609,9 @@ object GlobalDefinitions {
Modifiers = ExplodingRadialDegrade
}
portable_manned_turret_vs.Geometry = largeTurret
portable_manned_turret_vs.collision.xy = CollisionXYData(Array((0.01f, 10), (0.02f, 40), (0.03f, 60), (0.04f, 80), (0.05f, 100)))
portable_manned_turret_vs.collision.z = CollisionZData(Array((4f, 10), (4.25f, 40), (4.5f, 60), (4.75f, 80), (5f, 100)))
portable_manned_turret_vs.mass = 100f
deployable_shield_generator.Name = "deployable_shield_generator"
deployable_shield_generator.Descriptor = "ShieldGenerators"

View file

@ -10,6 +10,7 @@ import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.aura.AuraContainer
import net.psforever.objects.serverobject.environment.InteractWithEnvironment
import net.psforever.objects.serverobject.mount.MountableEntity
import net.psforever.objects.vital.resistance.ResistanceProfile
import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.interaction.DamageInteraction
@ -31,9 +32,10 @@ class Player(var avatar: Avatar)
with Container
with JammableUnit
with ZoneAware
with AuraContainer {
with AuraContainer
with MountableEntity {
interaction(new InteractWithEnvironment())
interaction(new InteractWithMines(range = 10))
interaction(new InteractWithMinesUnlessSpectating(obj = this, range = 10))
private var backpack: Boolean = false
private var released: Boolean = false
@ -594,3 +596,14 @@ object Player {
}
}
}
private class InteractWithMinesUnlessSpectating(
private val obj: Player,
range: Float
) extends InteractWithMines(range) {
override def interaction(target: InteractsWithZone): Unit = {
if (!obj.spectator) {
super.interaction(target)
}
}
}

View file

@ -101,6 +101,8 @@ class TurretControl(turret: TurretDeployable)
override protected def DestructionAwareness(target: Target, cause: DamageResult): Unit = {
super.DestructionAwareness(target, cause)
CancelJammeredSound(target)
CancelJammeredStatus(target)
Deployables.AnnounceDestroyDeployable(turret, None)
}

View file

@ -5,7 +5,7 @@ import net.psforever.objects.ce.InteractWithMines
import net.psforever.objects.definition.{ToolDefinition, VehicleDefinition}
import net.psforever.objects.equipment.{EquipmentSize, EquipmentSlot, JammableUnit}
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem, InventoryTile}
import net.psforever.objects.serverobject.mount.{Seat, SeatDefinition}
import net.psforever.objects.serverobject.mount.{MountableEntity, Seat, SeatDefinition}
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.aura.AuraContainer
@ -86,7 +86,8 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
with JammableUnit
with CommonNtuContainer
with Container
with AuraContainer {
with AuraContainer
with MountableEntity {
interaction(new InteractWithEnvironment())
interaction(new InteractWithMines(range = 30))
@ -110,11 +111,11 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
private var utilities: Map[Int, Utility] = Map()
private val trunk: GridInventory = GridInventory()
/**
/*
* Records the GUID of the cargo vehicle (galaxy/lodestar) this vehicle is stored in for DismountVehicleCargoMsg use
* DismountVehicleCargoMsg only passes the player_guid and this vehicle's guid
*/
private var mountedIn: Option[PlanetSideGUID] = None
//private var mountedIn: Option[PlanetSideGUID] = None
private var vehicleGatingManifest: Option[VehicleManifest] = None
private var previousVehicleGatingManifest: Option[VehicleManifest] = None
@ -142,22 +143,6 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
/** How long it takes to jack the vehicle in seconds, based on the hacker's certification level */
def JackingDuration: Array[Int] = Definition.JackingDuration
def MountedIn: Option[PlanetSideGUID] = {
this.mountedIn
}
def MountedIn_=(cargo_vehicle_guid: PlanetSideGUID): Option[PlanetSideGUID] = MountedIn_=(Some(cargo_vehicle_guid))
def MountedIn_=(cargo_vehicle_guid: Option[PlanetSideGUID]): Option[PlanetSideGUID] = {
cargo_vehicle_guid match {
case Some(_) =>
this.mountedIn = cargo_vehicle_guid
case None =>
this.mountedIn = None
}
MountedIn
}
override def Health_=(assignHealth: Int): Int = {
//TODO should vehicle class enforce this?
if (!Destroyed) {
@ -499,6 +484,10 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
def DamageModel = Definition.asInstanceOf[DamageResistanceModel]
override def BailProtection_=(protect: Boolean): Boolean = {
!Definition.CanFly && super.BailProtection_=(protect)
}
/**
* This is the definition entry that is used to store and unload pertinent information about the `Vehicle`.
* @return the vehicle's definition entry

View file

@ -232,25 +232,13 @@ object Vehicles {
log.info(s"${hacker.Name} has jacked a ${target.Definition.Name}")
val zone = target.Zone
// Forcefully dismount any cargo
target.CargoHolds.values.foreach(cargoHold => {
target.CargoHolds.foreach { case (index, cargoHold) =>
cargoHold.occupant match {
case Some(cargo: Vehicle) =>
cargo.Seats(0).occupant match {
case Some(_: Player) =>
CargoBehavior.HandleVehicleCargoDismount(
target.Zone,
cargo.GUID,
bailed = target.isFlying,
requestedByPassenger = false,
kicked = true
)
case _ =>
log.error("FinishHackingVehicle: vehicle in cargo hold missing driver")
CargoBehavior.HandleVehicleCargoDismount(cargo.GUID, cargo, target.GUID, target, bailed = false, requestedByPassenger = false, kicked = true)
}
cargo.Actor ! CargoBehavior.StartCargoDismounting(bailed = false)
case None => ;
}
})
}
// Forcefully dismount all seated occupants from the vehicle
target.Seats.values.foreach(seat => {
seat.occupant match {

View file

@ -32,6 +32,7 @@ import net.psforever.objects.locker.LockerContainerControl
import net.psforever.objects.serverobject.environment._
import net.psforever.objects.serverobject.repair.Repairable
import net.psforever.objects.serverobject.shuttle.OrbitalShuttlePad
import net.psforever.objects.vital.collision.CollisionReason
import net.psforever.objects.vital.environment.EnvironmentReason
import net.psforever.objects.vital.etc.{PainboxReason, SuicideReason}
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
@ -416,7 +417,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
(afterHolsters ++ afterInventory).foreach { entry => entry.obj.Faction = player.Faction }
afterHolsters.foreach {
case InventoryItem(citem: ConstructionItem, _) =>
Deployables.initializeConstructionAmmoMode(player.avatar.certifications, citem)
Deployables.initializeConstructionItem(player.avatar.certifications, citem)
case _ => ;
}
toDeleteOrDrop.foreach { entry => entry.obj.Faction = PlanetSideEmpire.NEUTRAL }
@ -795,7 +796,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
case _ =>
cause.interaction.cause.source.Aggravated.nonEmpty
}
//log historical event
//log historical event (always)
target.History(cause)
//stat changes
if (damageToCapacitor > 0) {
@ -803,11 +804,11 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
target.Name,
AvatarAction.PlanetsideAttributeSelf(targetGUID, 7, target.Capacitor.toLong)
)
announceConfrontation = true
announceConfrontation = true //TODO should we?
}
if (damageToStamina > 0) {
avatarActor ! AvatarActor.ConsumeStamina(damageToStamina)
announceConfrontation = true
announceConfrontation = true //TODO should we?
}
if (damageToHealth > 0) {
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 0, health))
@ -815,14 +816,19 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
}
val countableDamage = damageToHealth + damageToArmor
if(announceConfrontation) {
if (!aggravated) {
if (aggravated) {
events ! AvatarServiceMessage(
zoneId,
AvatarAction.SendResponse(Service.defaultPlayerGUID, AggravatedDamageMessage(targetGUID, countableDamage))
)
} else {
//activity on map
zone.Activity ! Zone.HotSpot.Activity(cause)
//alert to damage source
cause.adversarial match {
case Some(adversarial) =>
adversarial.attacker match {
case pSource : PlayerSource => //player damage
case pSource: PlayerSource => //player damage
val name = pSource.Name
zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match {
case Some(tplayer) =>
@ -855,6 +861,11 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
target.Name,
AvatarAction.EnvironmentalDamage(target.GUID, o.entity.GUID, countableDamage)
)
case _: CollisionReason =>
events ! AvatarServiceMessage(
zoneId,
AvatarAction.SendResponse(Service.defaultPlayerGUID, AggravatedDamageMessage(targetGUID, countableDamage))
)
case _ =>
zone.AvatarEvents ! AvatarServiceMessage(
target.Name,
@ -863,19 +874,6 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
}
}
}
else {
//general alert
zone.AvatarEvents ! AvatarServiceMessage(
target.Name,
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(countableDamage, Vector3.Zero))
)
}
}
if (aggravated) {
events ! AvatarServiceMessage(
zoneId,
AvatarAction.SendResponse(Service.defaultPlayerGUID, AggravatedDamageMessage(targetGUID, countableDamage))
)
}
}
@ -1137,7 +1135,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
//can not preserve ammo type in construction tool packets
citem.resetAmmoTypes()
}
Deployables.initializeConstructionAmmoMode(player.avatar.certifications, citem)
Deployables.initializeConstructionItem(player.avatar.certifications, citem)
case _ => ;
}

View file

@ -11,7 +11,7 @@ final case class DeployableSource(
faction: PlanetSideEmpire.Value,
health: Int,
shields: Int,
ownerName: String,
owner: SourceEntry,
position: Vector3,
orientation: Vector3
) extends SourceEntry {
@ -20,7 +20,7 @@ final case class DeployableSource(
def Definition: ObjectDefinition with DeployableDefinition = obj_def
def Health = health
def Shields = shields
def OwnerName = ownerName
def OwnerName = owner.Name
def Position = position
def Orientation = orientation
def Velocity = None
@ -29,12 +29,19 @@ final case class DeployableSource(
object DeployableSource {
def apply(obj: Deployable): DeployableSource = {
val ownerName = obj.OwnerName
val ownerSource = (obj.Zone.LivePlayers ++ obj.Zone.Corpses)
.find { p => ownerName.contains(p.Name) }
match {
case Some(p) => SourceEntry(p)
case _ => SourceEntry.None
}
DeployableSource(
obj.Definition,
obj.Faction,
obj.Health,
obj.Shields,
obj.OwnerName.getOrElse(""),
ownerSource,
obj.Position,
obj.Orientation
)

View file

@ -14,6 +14,7 @@ final case class VehicleSource(
position: Vector3,
orientation: Vector3,
velocity: Option[Vector3],
occupants: List[SourceEntry],
modifiers: ResistanceProfile
) extends SourceEntry {
override def Name = SourceEntry.NameFormat(obj_def.Name)
@ -37,6 +38,12 @@ object VehicleSource {
obj.Position,
obj.Orientation,
obj.Velocity,
obj.Seats.values.map { seat =>
seat.occupant match {
case Some(p) => PlayerSource(p)
case _ => SourceEntry.None
}
}.toList,
obj.Definition.asInstanceOf[ResistanceProfile]
)
}

View file

@ -8,7 +8,7 @@ import net.psforever.objects.definition.converter.SmallDeployableConverter
import net.psforever.objects.vital.damage.DamageCalculations
import net.psforever.objects.vital.resistance.ResistanceProfileMutators
import net.psforever.objects.vital.resolution.DamageResistanceModel
import net.psforever.objects.vital.{NoResistanceSelection, VitalityDefinition}
import net.psforever.objects.vital.{CollisionXYData, NoResistanceSelection, VitalityDefinition}
import scala.concurrent.duration._
@ -60,6 +60,7 @@ abstract class DeployableDefinition(objectId: Int)
ResistUsing = NoResistanceSelection
Packet = new SmallDeployableConverter
registerAs = "deployables"
collision.xy = new CollisionXYData(List((0f, 100)))
def Item: DeployedItem.Value = item
}

View file

@ -29,6 +29,7 @@ class ExoSuitDefinition(private val suitType: ExoSuitType.Value)
protected var capacitorRechargeDelayMillis: Int = 0
protected var capacitorRechargePerSecond: Int = 0
protected var capacitorDrainPerSecond: Int = 0
val collision: ExosuitCollisionData = new ExosuitCollisionData()
Name = "exo-suit"
DamageUsing = DamageCalculations.AgainstExoSuit
ResistUsing = StandardInfantryResistance

View file

@ -101,5 +101,15 @@ abstract class ObjectDefinition(private val objectId: Int) extends BasicDefiniti
Geometry
}
/**
* The maximum forward speed that can be expected to be achieved by this unit.
* Faster speeds are not discounted due to conditions of the motion or game environment
* but speeds too far beyond this measure should be considered suspicious.
* For ground vehicles, this field is called `maxForward` in the ADB.
* For flight vehicles, this field is called `MaxSpeed` and `flightmaxspeed` in the ADB,
* and it does not factor in the afterburner.
*/
var maxForwardSpeed: Float = 0f
def ObjectId: Int = objectId
}

View file

@ -197,6 +197,8 @@ class VehicleDefinition(objectId: Int)
obj.Actor ! akka.actor.PoisonPill
obj.Actor = Default.Actor
}
override val collision: AdvancedCollisionData = new AdvancedCollisionData()
}
object VehicleDefinition {

View file

@ -5,6 +5,7 @@ import akka.actor.Actor
import net.psforever.objects.{Vehicle, Vehicles}
import net.psforever.objects.equipment.JammableUnit
import net.psforever.objects.serverobject.damage.Damageable.Target
import net.psforever.objects.vital.base.DamageResolution
import net.psforever.objects.vital.interaction.DamageResult
import net.psforever.objects.vital.resolution.ResolutionCalculations
import net.psforever.services.Service
@ -103,13 +104,13 @@ trait DamageableVehicle
case _ => (0, 0, 0)
}
var announceConfrontation: Boolean = reportDamageToVehicle || totalDamage > 0
val aggravated = TryAggravationEffectActivate(cause) match {
val showAsAggravated = (TryAggravationEffectActivate(cause) match {
case Some(_) =>
announceConfrontation = true
false
case _ =>
cause.interaction.cause.source.Aggravated.nonEmpty
}
}) || cause.interaction.cause.resolution == DamageResolution.Collision
reportDamageToVehicle = false
if (obj.MountedIn.nonEmpty) {
@ -139,7 +140,7 @@ trait DamageableVehicle
}
}
if (announceConfrontation) {
if (aggravated) {
if (showAsAggravated) {
val msg = VehicleAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(totalDamage, Vector3.Zero))
obj.Seats.values
.collect { case seat if seat.occupant.nonEmpty => seat.occupant.get.Name }

View file

@ -3,6 +3,7 @@ package net.psforever.objects.serverobject.mount
import akka.actor.ActorRef
import net.psforever.objects.Player
import net.psforever.types.BailType
import scala.annotation.tailrec
@ -97,7 +98,11 @@ object Mountable {
* @param player the player who sent this request message
* @param seat_num the seat index
*/
final case class TryDismount(player: Player, seat_num: Int)
final case class TryDismount(player: Player, seat_num: Int, bailType: BailType.Value)
object TryDismount {
def apply(player: Player, seatNum: Int): TryDismount = TryDismount(player, seatNum, BailType.Normal)
}
/**
* A basic `Trait` connecting all of the actionable `Mountable` response messages.

View file

@ -7,6 +7,7 @@ import net.psforever.objects.Player
import net.psforever.objects.entity.WorldEntity
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.types.BailType
import scala.collection.mutable
@ -82,9 +83,9 @@ trait MountableBehavior {
* @see `Mountable`
*/
val dismountBehavior: Receive = {
case Mountable.TryDismount(user, seat_number) =>
case Mountable.TryDismount(user, seat_number, bail_type) =>
val obj = MountableObject
if (dismountTest(obj, seat_number, user) && tryDismount(obj, seat_number, user)) {
if (dismountTest(obj, seat_number, user) && tryDismount(obj, seat_number, user, bail_type)) {
user.VehicleSeated = None
obj.Zone.actor ! ZoneActor.AddToBlockMap(user, obj.Position)
sender() ! Mountable.MountMessages(
@ -112,11 +113,12 @@ trait MountableBehavior {
private def tryDismount(
obj: Mountable,
seatNumber: Int,
user: Player
user: Player,
bailType: BailType.Value
): Boolean = {
obj.Seats.get(seatNumber) match {
case Some(seat) => seat.unmount(user).isEmpty
case _ => false
case Some(seat) => seat.unmount(user, bailType).isEmpty
case _ => false
}
}
}

View file

@ -0,0 +1,26 @@
// Copyright (c) 2021 PSForever
package net.psforever.objects.serverobject.mount
import net.psforever.types.PlanetSideGUID
trait MountableEntity {
private var bailProtection: Boolean = false
def BailProtection: Boolean = bailProtection
def BailProtection_=(protect: Boolean) = {
bailProtection = protect
BailProtection
}
private var mountedIn: Option[PlanetSideGUID] = None
def MountedIn: Option[PlanetSideGUID] = mountedIn
def MountedIn_=(cargo_guid: PlanetSideGUID): Option[PlanetSideGUID] = MountedIn_=(Some(cargo_guid))
def MountedIn_=(cargo_guid: Option[PlanetSideGUID]): Option[PlanetSideGUID] = {
mountedIn = cargo_guid
MountedIn
}
}

View file

@ -1,7 +1,9 @@
// Copyright (c) 2021 PSForever
package net.psforever.objects.serverobject.mount
trait MountableSpace[A] {
import net.psforever.types.BailType
trait MountableSpace[A <: MountableEntity] {
private var _occupant: Option[A] = None
/**
@ -53,6 +55,7 @@ trait MountableSpace[A] {
target match {
case Some(p) if testToMount(p) =>
_occupant = target
p.BailProtection = false
target
case _ =>
occupant
@ -63,7 +66,7 @@ trait MountableSpace[A] {
* Tests whether the target is allowed to be mounted.
* @see `MountableSpace[A].canBeOccupiedBy(A)`
*/
protected def testToMount(target: A): Boolean = canBeOccupied && canBeOccupiedBy(target)
protected def testToMount(target: A): Boolean = target.MountedIn.isEmpty && canBeOccupied && canBeOccupiedBy(target)
/**
* Attempt to dismount the target entity from this space.
@ -73,10 +76,22 @@ trait MountableSpace[A] {
/**
* Attempt to dismount the target entity from this space.
*/
def unmount(target: Option[A]): Option[A] = {
def unmount(target: A, bailType: BailType.Value): Option[A] = unmount(Some(target), bailType)
/**
* Attempt to dismount the target entity from this space.
*/
def unmount(target: Option[A]): Option[A] = unmount(target, BailType.Normal)
/**
* Attempt to dismount the target entity from this space.
* @return the current seat occupant, which should be `None` if the operation was successful
*/
def unmount(target: Option[A], bailType: BailType.Value): Option[A] = {
target match {
case Some(p) if testToUnmount(p) =>
_occupant = None
p.BailProtection = bailable && (bailType == BailType.Bailed || bailType == BailType.Kicked)
None
case _ =>
occupant

View file

@ -5,7 +5,5 @@ import net.psforever.objects.Vehicle
import net.psforever.objects.serverobject.mount.{MountableSpace, MountableSpaceDefinition}
class Cargo(private val cdef: MountableSpaceDefinition[Vehicle]) extends MountableSpace[Vehicle] {
override protected def testToMount(target: Vehicle): Boolean = target.MountedIn.isEmpty && super.testToMount(target)
def definition: MountableSpaceDefinition[Vehicle] = cdef
}

View file

@ -1,24 +1,12 @@
// Copyright (c) 2020 PSForever
package net.psforever.objects.vehicles
import akka.actor.{Actor, Cancellable}
import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.zones.Zone
import akka.actor.Actor
import net.psforever.objects._
import net.psforever.objects.vehicles.CargoBehavior.{CheckCargoDismount, CheckCargoMounting}
import net.psforever.packet.game.{CargoMountPointStatusMessage, ObjectAttachMessage, ObjectDetachMessage, PlanetsideAttributeMessage}
import net.psforever.types.{CargoStatus, PlanetSideGUID, Vector3}
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.Service
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import scala.concurrent.duration._
import net.psforever.types.PlanetSideGUID
trait CargoBehavior {
_: Actor =>
private var cargoMountTimer: Cancellable = Default.Cancellable
private var cargoDismountTimer: Cancellable = Default.Cancellable
/* gate-keep mounting behavior so that unit does not try to dismount as cargo, or mount different vehicle */
private var isMounting: Option[PlanetSideGUID] = None
/* gate-keep dismounting behavior so that unit does not try to mount as cargo, or dismount from different vehicle */
@ -26,547 +14,79 @@ trait CargoBehavior {
def CargoObject: Vehicle
def endAllCargoOperations(): Unit = {
val obj = CargoObject
val zone = obj.Zone
zone.GUID(isMounting) match {
case Some(v : Vehicle) => v.Actor ! CargoBehavior.EndCargoMounting(obj.GUID)
case _ => ;
}
isMounting = None
zone.GUID(isDismounting) match {
case Some(v: Vehicle) => v.Actor ! CargoBehavior.EndCargoDismounting(obj.GUID)
case _ => ;
}
isDismounting = None
startCargoDismounting(bailed = false)
}
val cargoBehavior: Receive = {
case CheckCargoMounting(carrier_guid, mountPoint, iteration) =>
val obj = CargoObject
if (
(isMounting.isEmpty || isMounting.contains(carrier_guid)) && isDismounting.isEmpty &&
CargoBehavior.HandleCheckCargoMounting(obj.Zone, carrier_guid, obj.GUID, obj, mountPoint, iteration)
) {
if (iteration == 0) {
//open the cargo bay door
obj.Zone.AvatarEvents ! AvatarServiceMessage(
obj.Zone.id,
AvatarAction.SendResponse(
Service.defaultPlayerGUID,
CargoMountPointStatusMessage(
carrier_guid,
PlanetSideGUID(0),
obj.GUID,
PlanetSideGUID(0),
mountPoint,
CargoStatus.InProgress,
0
)
)
)
}
isMounting = Some(carrier_guid)
import scala.concurrent.ExecutionContext.Implicits.global
cargoMountTimer.cancel()
cargoMountTimer = context.system.scheduler.scheduleOnce(
250 milliseconds,
self,
CheckCargoMounting(carrier_guid, mountPoint, iteration + 1)
)
} else {
case CargoBehavior.StartCargoMounting(carrier_guid, mountPoint) =>
startCargoMounting(carrier_guid, mountPoint)
case CargoBehavior.StartCargoDismounting(bailed) =>
startCargoDismounting(bailed)
case CargoBehavior.EndCargoMounting(carrier_guid) =>
if (isMounting.contains(carrier_guid)) {
isMounting = None
}
case CheckCargoDismount(carrier_guid, mountPoint, iteration) =>
val obj = CargoObject
if (
(isDismounting.isEmpty || isDismounting.contains(carrier_guid)) && isMounting.isEmpty &&
CargoBehavior.HandleCheckCargoDismounting(obj.Zone, carrier_guid, obj.GUID, obj, mountPoint, iteration)
) {
isDismounting = Some(carrier_guid)
import scala.concurrent.ExecutionContext.Implicits.global
cargoDismountTimer.cancel()
cargoDismountTimer = context.system.scheduler.scheduleOnce(
250 milliseconds,
self,
CheckCargoDismount(carrier_guid, mountPoint, iteration + 1)
)
} else {
case CargoBehavior.EndCargoDismounting(carrier_guid) =>
if (isDismounting.contains(carrier_guid)) {
isDismounting = None
}
}
def startCargoMounting(carrier_guid: PlanetSideGUID, mountPoint: Int): Unit = {
val obj = CargoObject
obj.Zone.GUID(carrier_guid) match {
case Some(carrier: Vehicle)
if isMounting.isEmpty && isDismounting.isEmpty && (carrier.CargoHolds.get(mountPoint) match {
case Some(hold) => !hold.isOccupied
case _ => false
}) =>
isMounting = Some(carrier_guid)
carrier.Actor ! CarrierBehavior.CheckCargoMounting(obj.GUID, mountPoint, 0)
case _ => ;
isMounting = None
}
}
def startCargoDismounting(bailed: Boolean): Unit = {
val obj = CargoObject
obj.Zone.GUID(obj.MountedIn) match {
case Some(carrier: Vehicle) =>
carrier.CargoHolds.find { case (_, hold) => hold.occupant.contains(obj) } match {
case Some((mountPoint, _))
if isDismounting.isEmpty && isMounting.isEmpty =>
isDismounting = obj.MountedIn
carrier.Actor ! CarrierBehavior.CheckCargoDismount(obj.GUID, mountPoint, 0, bailed)
case _ =>
obj.MountedIn = None
isDismounting = None
}
case _ =>
obj.MountedIn = None
isDismounting = None
}
}
}
object CargoBehavior {
private val log = org.log4s.getLogger("CargoBehavior")
final case class CheckCargoMounting(carrier_guid: PlanetSideGUID, cargo_mountpoint: Int, iteration: Int)
final case class CheckCargoDismount(carrier_guid: PlanetSideGUID, cargo_mountpoint: Int, iteration: Int)
/**
* na
* @param carrierGUID the ferrying carrier vehicle
* @param cargoGUID the vehicle being ferried as cargo
* @param cargo the vehicle being ferried as cargo
* @param mountPoint the cargo hold to which the cargo vehicle is stowed
* @param iteration number of times a proper mounting for this combination has been queried
*/
def HandleCheckCargoMounting(
zone: Zone,
carrierGUID: PlanetSideGUID,
cargoGUID: PlanetSideGUID,
cargo: Vehicle,
mountPoint: Int,
iteration: Int
): Boolean = {
zone.GUID(carrierGUID) match {
case Some(carrier: Vehicle) =>
HandleCheckCargoMounting(cargoGUID, cargo, carrierGUID, carrier, mountPoint, iteration)
case carrier if iteration > 0 =>
log.warn(s"HandleCheckCargoMounting: participant vehicles changed in the middle of a mounting event")
LogCargoEventMissingVehicleError("HandleCheckCargoMounting: carrier", carrier, carrierGUID)
false
case _ =>
false
}
}
/**
* na
* @param cargoGUID the vehicle being ferried as cargo
* @param cargo the vehicle being ferried as cargo
* @param carrierGUID the ferrying carrier vehicle
* @param carrier the ferrying carrier vehicle
* @param mountPoint the cargo hold to which the cargo vehicle is stowed
* @param iteration number of times a proper mounting for this combination has been queried
*/
private def HandleCheckCargoMounting(
cargoGUID: PlanetSideGUID,
cargo: Vehicle,
carrierGUID: PlanetSideGUID,
carrier: Vehicle,
mountPoint: Int,
iteration: Int
): Boolean = {
val zone = carrier.Zone
val distance = Vector3.DistanceSquared(cargo.Position, carrier.Position)
carrier.CargoHold(mountPoint) match {
case Some(hold) if !hold.isOccupied =>
log.debug(
s"HandleCheckCargoMounting: mount distance between $cargoGUID and $carrierGUID - actual=$distance, target=64"
)
if (distance <= 64) {
//cargo vehicle is close enough to assume to be physically within the carrier's hold; mount it
log.debug(s"HandleCheckCargoMounting: mounting cargo vehicle in carrier at distance of $distance")
hold.mount(cargo)
cargo.MountedIn = carrierGUID
cargo.Velocity = None
zone.VehicleEvents ! VehicleServiceMessage(
s"${cargo.Actor}",
VehicleAction.SendResponse(PlanetSideGUID(0), PlanetsideAttributeMessage(cargoGUID, 0, cargo.Health))
)
zone.VehicleEvents ! VehicleServiceMessage(
s"${cargo.Actor}",
VehicleAction.SendResponse(PlanetSideGUID(0), PlanetsideAttributeMessage(cargoGUID, 68, cargo.Shields))
)
CargoMountBehaviorForAll(carrier, cargo, mountPoint)
zone.actor ! ZoneActor.RemoveFromBlockMap(cargo)
false
} else if (distance > 625 || iteration >= 40) {
//vehicles moved too far away or took too long to get into proper position; abort mounting
log.debug(
"HandleCheckCargoMounting: cargo vehicle is too far away or didn't mount within allocated time - aborting"
)
val cargoDriverGUID = cargo.Seats(0).occupant.get.GUID
zone.VehicleEvents ! VehicleServiceMessage(
zone.id,
VehicleAction.SendResponse(
cargoDriverGUID,
CargoMountPointStatusMessage(
carrierGUID,
PlanetSideGUID(0),
PlanetSideGUID(0),
cargoGUID,
mountPoint,
CargoStatus.Empty,
0
)
)
)
false
//sending packet to the cargo vehicle's client results in player being lock in own vehicle
//player gets stuck as "always trying to remount the cargo hold"
//obviously, don't do this
} else {
//cargo vehicle still not in position but there is more time to wait; reschedule check
true
}
case None =>
;
log.warn(s"HandleCheckCargoMounting: carrier vehicle $carrier does not have a cargo hold #$mountPoint")
false
case _ =>
if (iteration == 0) {
log.warn(
s"HandleCheckCargoMounting: carrier vehicle $carrier already possesses cargo in hold #$mountPoint; this operation was initiated incorrectly"
)
} else {
log.error(
s"HandleCheckCargoMounting: something has attached to the carrier vehicle $carrier cargo of hold #$mountPoint while a cargo dismount event was ongoing; stopped at iteration $iteration / 40"
)
}
false
}
}
/**
* na
* @param cargoGUID na
* @param carrierGUID na
* @param mountPoint na
* @param iteration na
*/
def HandleCheckCargoDismounting(
zone: Zone,
carrierGUID: PlanetSideGUID,
cargoGUID: PlanetSideGUID,
cargo: Vehicle,
mountPoint: Int,
iteration: Int
): Boolean = {
zone.GUID(carrierGUID) match {
case Some(carrier: Vehicle) =>
HandleCheckCargoDismounting(cargoGUID, cargo, carrierGUID, carrier, mountPoint, iteration)
case carrier if iteration > 0 =>
log.error(s"HandleCheckCargoDismounting: participant vehicles changed in the middle of a mounting event")
LogCargoEventMissingVehicleError("HandleCheckCargoDismounting: carrier", carrier, carrierGUID)
false
case _ =>
false
}
}
/**
* na
* @param cargoGUID na
* @param cargo na
* @param carrierGUID na
* @param carrier na
* @param mountPoint na
* @param iteration na
*/
private def HandleCheckCargoDismounting(
cargoGUID: PlanetSideGUID,
cargo: Vehicle,
carrierGUID: PlanetSideGUID,
carrier: Vehicle,
mountPoint: Int,
iteration: Int
): Boolean = {
val zone = carrier.Zone
carrier.CargoHold(mountPoint) match {
case Some(hold) if !hold.isOccupied =>
val distance = Vector3.DistanceSquared(cargo.Position, carrier.Position)
log.debug(
s"HandleCheckCargoDismounting: mount distance between $cargoGUID and $carrierGUID - actual=$distance, target=225"
)
if (distance > 225) {
//cargo vehicle has moved far enough away; close the carrier's hold door
log.debug(
s"HandleCheckCargoDismounting: dismount of cargo vehicle from carrier complete at distance of $distance"
)
val cargoDriverGUID = cargo.Seats(0).occupant.get.GUID
zone.VehicleEvents ! VehicleServiceMessage(
zone.id,
VehicleAction.SendResponse(
cargoDriverGUID,
CargoMountPointStatusMessage(
carrierGUID,
PlanetSideGUID(0),
PlanetSideGUID(0),
cargoGUID,
mountPoint,
CargoStatus.Empty,
0
)
)
)
false
//sending packet to the cargo vehicle's client results in player being lock in own vehicle
//player gets stuck as "always trying to remount the cargo hold"
//obviously, don't do this
} else if (iteration > 40) {
//cargo vehicle has spent too long not getting far enough away; restore the cargo's mount in the carrier hold
hold.mount(cargo)
cargo.MountedIn = carrierGUID
CargoMountBehaviorForAll(carrier, cargo, mountPoint)
zone.actor ! ZoneActor.RemoveFromBlockMap(cargo)
false
} else {
//cargo vehicle did not move far away enough yet and there is more time to wait; reschedule check
true
}
case None =>
log.warn(s"HandleCheckCargoDismounting: carrier vehicle $carrier does not have a cargo hold #$mountPoint")
false
case _ =>
if (iteration == 0) {
log.warn(
s"HandleCheckCargoDismounting: carrier vehicle $carrier will not discharge the cargo of hold #$mountPoint; this operation was initiated incorrectly"
)
} else {
log.error(
s"HandleCheckCargoDismounting: something has attached to the carrier vehicle $carrier cargo of hold #$mountPoint while a cargo dismount event was ongoing; stopped at iteration $iteration / 40"
)
}
false
}
}
/**
* na
* @param zone na
* @param cargo_guid na
* @param bailed na
* @param requestedByPassenger na
* @param kicked na
*/
def HandleVehicleCargoDismount(
zone: Zone,
cargo_guid: PlanetSideGUID,
bailed: Boolean,
requestedByPassenger: Boolean,
kicked: Boolean
): Unit = {
zone.GUID(cargo_guid) match {
case Some(cargo: Vehicle) =>
zone.GUID(cargo.MountedIn) match {
case Some(ferry: Vehicle) =>
HandleVehicleCargoDismount(cargo_guid, cargo, ferry.GUID, ferry, bailed, requestedByPassenger, kicked)
case _ =>
log.warn(
s"DismountVehicleCargo: target ${cargo.Definition.Name}@$cargo_guid does not know what treats it as cargo"
)
}
case _ =>
log.warn(s"DismountVehicleCargo: target $cargo_guid either is not a vehicle in ${zone.id} or does not exist")
}
}
/**
* na
* @param cargoGUID the globally unique number for the vehicle being ferried
* @param cargo the vehicle being ferried
* @param carrierGUID the globally unique number for the vehicle doing the ferrying
* @param carrier the vehicle doing the ferrying
* @param bailed the ferried vehicle is bailing from the cargo hold
* @param requestedByPassenger the ferried vehicle is being politely disembarked from the cargo hold
* @param kicked the ferried vehicle is being kicked out of the cargo hold
*/
def HandleVehicleCargoDismount(
cargoGUID: PlanetSideGUID,
cargo: Vehicle,
carrierGUID: PlanetSideGUID,
carrier: Vehicle,
bailed: Boolean,
requestedByPassenger: Boolean,
kicked: Boolean
): Unit = {
val zone = carrier.Zone
carrier.CargoHolds.find({ case (_, hold) => hold.occupant.contains(cargo) }) match {
case Some((mountPoint, hold)) =>
cargo.MountedIn = None
hold.unmount(cargo)
val driverOpt = cargo.Seats(0).occupant
val rotation: Vector3 = if (Vehicles.CargoOrientation(cargo) == 1) { //TODO: BFRs will likely also need this set
//dismount router "sideways" in a lodestar
carrier.Orientation.xy + Vector3.z((carrier.Orientation.z - 90) % 360)
} else {
carrier.Orientation
}
val cargoHoldPosition: Vector3 = if (carrier.Definition == GlobalDefinitions.dropship) {
//the galaxy cargo bay is offset backwards from the center of the vehicle
carrier.Position + Vector3.Rz(Vector3(0, 7, 0), math.toRadians(carrier.Orientation.z))
} else {
//the lodestar's cargo hold is almost the center of the vehicle
carrier.Position
}
val GUID0 = Service.defaultPlayerGUID
val zoneId = zone.id
val events = zone.VehicleEvents
val cargoActor = cargo.Actor
events ! VehicleServiceMessage(
s"$cargoActor",
VehicleAction.SendResponse(GUID0, PlanetsideAttributeMessage(cargoGUID, 0, cargo.Health))
)
events ! VehicleServiceMessage(
s"$cargoActor",
VehicleAction.SendResponse(GUID0, PlanetsideAttributeMessage(cargoGUID, 68, cargo.Shields))
)
zone.actor ! ZoneActor.AddToBlockMap(cargo, carrier.Position)
if (carrier.isFlying) {
//the carrier vehicle is flying; eject the cargo vehicle
val ejectCargoMsg =
CargoMountPointStatusMessage(carrierGUID, GUID0, GUID0, cargoGUID, mountPoint, CargoStatus.InProgress, 0)
val detachCargoMsg = ObjectDetachMessage(carrierGUID, cargoGUID, cargoHoldPosition - Vector3.z(1), rotation)
val resetCargoMsg =
CargoMountPointStatusMessage(carrierGUID, GUID0, GUID0, cargoGUID, mountPoint, CargoStatus.Empty, 0)
events ! VehicleServiceMessage(zoneId, VehicleAction.SendResponse(GUID0, ejectCargoMsg))
events ! VehicleServiceMessage(zoneId, VehicleAction.SendResponse(GUID0, detachCargoMsg))
events ! VehicleServiceMessage(zoneId, VehicleAction.SendResponse(GUID0, resetCargoMsg))
log.debug(s"HandleVehicleCargoDismount: eject - $ejectCargoMsg, detach - $detachCargoMsg")
if (driverOpt.isEmpty) {
//TODO cargo should drop like a rock like normal; until then, deconstruct it
cargo.Actor ! Vehicle.Deconstruct()
}
} else {
//the carrier vehicle is not flying; just open the door and let the cargo vehicle back out; force it out if necessary
val cargoStatusMessage =
CargoMountPointStatusMessage(carrierGUID, GUID0, cargoGUID, GUID0, mountPoint, CargoStatus.InProgress, 0)
val cargoDetachMessage =
ObjectDetachMessage(carrierGUID, cargoGUID, cargoHoldPosition + Vector3.z(1f), rotation)
events ! VehicleServiceMessage(zoneId, VehicleAction.SendResponse(GUID0, cargoStatusMessage))
events ! VehicleServiceMessage(zoneId, VehicleAction.SendResponse(GUID0, cargoDetachMessage))
driverOpt match {
case Some(driver) =>
events ! VehicleServiceMessage(
s"${driver.Name}",
VehicleAction.KickCargo(GUID0, cargo, cargo.Definition.AutoPilotSpeed2, 2500)
)
//check every quarter second if the vehicle has moved far enough away to be considered dismounted
cargoActor ! CheckCargoDismount(carrierGUID, mountPoint, 0)
case None =>
val resetCargoMsg =
CargoMountPointStatusMessage(carrierGUID, GUID0, GUID0, cargoGUID, mountPoint, CargoStatus.Empty, 0)
events ! VehicleServiceMessage(zoneId, VehicleAction.SendResponse(GUID0, resetCargoMsg)) //lazy
//TODO cargo should back out like normal; until then, deconstruct it
cargoActor ! Vehicle.Deconstruct()
}
}
case None =>
log.warn(s"HandleDismountVehicleCargo: can not locate cargo $cargo in any hold of the carrier vehicle $carrier")
}
}
//logging and messaging support functions
/**
* na
* @param decorator custom text for these messages in the log
* @param target an optional the target object
* @param targetGUID the expected globally unique identifier of the target object
*/
def LogCargoEventMissingVehicleError(
decorator: String,
target: Option[PlanetSideGameObject],
targetGUID: PlanetSideGUID
): Unit = {
target match {
case Some(_: Vehicle) => ;
case Some(_) => log.error(s"$decorator target $targetGUID no longer identifies as a vehicle")
case None => log.error(s"$decorator target $targetGUID has gone missing")
}
}
/**
* Produce an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
* that will set up a realized parent-child association between a ferrying vehicle and a ferried vehicle.
* @see `CargoMountPointStatusMessage`
* @see `ObjectAttachMessage`
* @see `Vehicles.CargoOrientation`
* @param carrier the ferrying vehicle
* @param cargo the ferried vehicle
* @param mountPoint the point on the ferryoing vehicle where the ferried vehicle is attached;
* also known as a "cargo hold"
* @return a tuple composed of an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
*/
def CargoMountMessages(
carrier: Vehicle,
cargo: Vehicle,
mountPoint: Int
): (ObjectAttachMessage, CargoMountPointStatusMessage) = {
CargoMountMessages(carrier.GUID, cargo.GUID, mountPoint, Vehicles.CargoOrientation(cargo))
}
/**
* Produce an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
* that will set up a realized parent-child association between a ferrying vehicle and a ferried vehicle.
* @see `CargoMountPointStatusMessage`
* @see `ObjectAttachMessage`
* @param carrierGUID the ferrying vehicle
* @param cargoGUID the ferried vehicle
* @param mountPoint the point on the ferryoing vehicle where the ferried vehicle is attached
* @param orientation the positioning of the cargo vehicle in the carrier cargo bay
* @return a tuple composed of an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
*/
def CargoMountMessages(
carrierGUID: PlanetSideGUID,
cargoGUID: PlanetSideGUID,
mountPoint: Int,
orientation: Int
): (ObjectAttachMessage, CargoMountPointStatusMessage) = {
(
ObjectAttachMessage(carrierGUID, cargoGUID, mountPoint),
CargoMountPointStatusMessage(
carrierGUID,
cargoGUID,
cargoGUID,
PlanetSideGUID(0),
mountPoint,
CargoStatus.Occupied,
orientation
)
)
}
/**
* Dispatch an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet to all other clients, not this one.
* @see `CargoMountPointStatusMessage`
* @see `ObjectAttachMessage`
* @param carrier the ferrying vehicle
* @param cargo the ferried vehicle
* @param mountPoint the point on the ferryoing vehicle where the ferried vehicle is attached
* @return a tuple composed of an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
*/
def CargoMountBehaviorForOthers(
carrier: Vehicle,
cargo: Vehicle,
mountPoint: Int,
exclude: PlanetSideGUID
): (ObjectAttachMessage, CargoMountPointStatusMessage) = {
val msgs @ (attachMessage, mountPointStatusMessage) = CargoMountMessages(carrier, cargo, mountPoint)
CargoMountMessagesForOthers(carrier.Zone, exclude, attachMessage, mountPointStatusMessage)
msgs
}
/**
* Dispatch an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet to all other clients, not this one.
* @see `CargoMountPointStatusMessage`
* @see `ObjectAttachMessage`
* @param attachMessage an `ObjectAttachMessage` packet suitable for initializing cargo operations
* @param mountPointStatusMessage a `CargoMountPointStatusMessage` packet suitable for initializing cargo operations
*/
def CargoMountMessagesForOthers(
zone: Zone,
exclude: PlanetSideGUID,
attachMessage: ObjectAttachMessage,
mountPointStatusMessage: CargoMountPointStatusMessage
): Unit = {
zone.VehicleEvents ! VehicleServiceMessage(zone.id, VehicleAction.SendResponse(exclude, attachMessage))
zone.VehicleEvents ! VehicleServiceMessage(zone.id, VehicleAction.SendResponse(exclude, mountPointStatusMessage))
}
/**
* Dispatch an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet to everyone.
* @see `CargoMountPointStatusMessage`
* @see `ObjectAttachMessage`
* @param carrier the ferrying vehicle
* @param cargo the ferried vehicle
* @param mountPoint the point on the ferryoing vehicle where the ferried vehicle is attached
* @return a tuple composed of an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
*/
def CargoMountBehaviorForAll(
carrier: Vehicle,
cargo: Vehicle,
mountPoint: Int
): (ObjectAttachMessage, CargoMountPointStatusMessage) = {
val zone = carrier.Zone
val zoneId = zone.id
val msgs @ (attachMessage, mountPointStatusMessage) = CargoMountMessages(carrier, cargo, mountPoint)
zone.VehicleEvents ! VehicleServiceMessage(
zoneId,
VehicleAction.SendResponse(Service.defaultPlayerGUID, attachMessage)
)
zone.VehicleEvents ! VehicleServiceMessage(
zoneId,
VehicleAction.SendResponse(Service.defaultPlayerGUID, mountPointStatusMessage)
)
msgs
}
final case class StartCargoMounting(cargo_guid: PlanetSideGUID, cargo_mountpoint: Int)
final case class StartCargoDismounting(bailed: Boolean)
final case class EndCargoMounting(carrier_guid: PlanetSideGUID)
final case class EndCargoDismounting(carrier_guid: PlanetSideGUID)
}

View file

@ -1,15 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.vehicles
/**
* An `Enumeration` of exo-suit-based mount access restrictions.<br>
* <br>
* The default value is `NoMax` as that is the most common mount.
* `NoReinforcedOrMax` is next most common.
* `MaxOnly` is a rare mount restriction found in pairs on Galaxies and on the large "Ground Transport" vehicles.
*/
object CargoVehicleRestriction extends Enumeration {
type Type = Value
val Small, Large = Value
}

View file

@ -0,0 +1,657 @@
// Copyright (c) 2021 PSForever
package net.psforever.objects.vehicles
import akka.actor.{Actor, Cancellable}
import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.zones.Zone
import net.psforever.objects._
import net.psforever.packet.game.{
CargoMountPointStatusMessage,
ObjectAttachMessage,
ObjectDetachMessage,
PlanetsideAttributeMessage
}
import net.psforever.types.{BailType, CargoStatus, PlanetSideGUID, Vector3}
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.Service
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import scala.concurrent.duration._
trait CarrierBehavior {
_: Actor =>
private var cargoMountTimer: Cancellable = Default.Cancellable
private var cargoDismountTimer: Cancellable = Default.Cancellable
/* gate-keep mounting behavior so that another vehicle does not attempt to mount, or dismount in the middle */
private var isMounting: Option[PlanetSideGUID] = None
/* gate-keep dismounting behavior so that another vehicle does not attempt to dismount, or dismount in the middle */
private var isDismounting: Option[PlanetSideGUID] = None
def CarrierObject: Vehicle
def endAllCarrierOperations(): Unit = {
cargoMountTimer.cancel()
cargoDismountTimer.cancel()
val obj = CarrierObject
val zone = obj.Zone
zone.GUID(isMounting) match {
case Some(v : Vehicle) => v.Actor ! CargoBehavior.EndCargoMounting(obj.GUID)
case _ => ;
}
isMounting = None
zone.GUID(isDismounting) match {
case Some(v : Vehicle) => v.Actor ! CargoBehavior.EndCargoDismounting(obj.GUID)
case _ => ;
}
isDismounting = None
}
val carrierBehavior: Receive = {
case CarrierBehavior.CheckCargoMounting(cargo_guid, mountPoint, iteration) =>
checkCargoMounting(cargo_guid, mountPoint, iteration)
case CarrierBehavior.CheckCargoDismount(cargo_guid, mountPoint, iteration, bailed) =>
checkCargoDismount(cargo_guid, mountPoint, iteration, bailed)
}
def checkCargoMounting(cargo_guid: PlanetSideGUID, mountPoint: Int, iteration: Int): Unit = {
val obj = CarrierObject
if (
(isMounting.isEmpty || isMounting.contains(cargo_guid)) && isDismounting.isEmpty &&
CarrierBehavior.HandleCheckCargoMounting(obj.Zone, obj.GUID, cargo_guid, obj, mountPoint, iteration)
) {
if (iteration == 0) {
//open the cargo bay door
obj.Zone.AvatarEvents ! AvatarServiceMessage(
obj.Zone.id,
AvatarAction.SendResponse(
Service.defaultPlayerGUID,
CargoMountPointStatusMessage(
obj.GUID,
PlanetSideGUID(0),
cargo_guid,
PlanetSideGUID(0),
mountPoint,
CargoStatus.InProgress,
0
)
)
)
}
isMounting = Some(cargo_guid)
import scala.concurrent.ExecutionContext.Implicits.global
cargoMountTimer.cancel()
cargoMountTimer = context.system.scheduler.scheduleOnce(
250 milliseconds,
self,
CarrierBehavior.CheckCargoMounting(cargo_guid, mountPoint, iteration + 1)
)
}
else {
obj.Zone.GUID(isMounting) match {
case Some(v: Vehicle) => v.Actor ! CargoBehavior.EndCargoMounting(obj.GUID)
case _ => ;
}
isMounting = None
}
}
def checkCargoDismount(cargo_guid: PlanetSideGUID, mountPoint: Int, iteration: Int, bailed: Boolean): Unit = {
val obj = CarrierObject
val zone = obj.Zone
val guid = obj.GUID
if ((isDismounting.isEmpty || isDismounting.contains(cargo_guid)) && isMounting.isEmpty) {
val prolongedDismount = if (iteration == 0) {
zone.GUID(cargo_guid) match {
case Some(cargo : Vehicle) =>
CarrierBehavior.HandleVehicleCargoDismount(
cargo_guid,
cargo,
guid,
obj,
bailed,
requestedByPassenger = false,
kicked = false
)
case _ =>
obj.CargoHold(mountPoint) match {
case Some(hold) if hold.isOccupied && hold.occupant.get.GUID == cargo_guid =>
hold.unmount(hold.occupant.get)
case _ => ;
}
false
}
} else {
CarrierBehavior.HandleCheckCargoDismounting(zone, guid, cargo_guid, obj, mountPoint, iteration, bailed)
}
if (prolongedDismount) {
isDismounting = Some(cargo_guid)
import scala.concurrent.ExecutionContext.Implicits.global
cargoDismountTimer.cancel()
cargoDismountTimer = context.system.scheduler.scheduleOnce(
250 milliseconds,
self,
CarrierBehavior.CheckCargoDismount(cargo_guid, mountPoint, iteration + 1, bailed)
)
} else {
zone.GUID(isDismounting.getOrElse(cargo_guid)) match {
case Some(cargo: Vehicle) =>
cargo.Actor ! CargoBehavior.EndCargoDismounting(guid)
case _ => ;
}
isDismounting = None
}
} else {
zone.GUID(isDismounting.getOrElse(cargo_guid)) match {
case Some(cargo: Vehicle) => cargo.Actor ! CargoBehavior.EndCargoDismounting(guid)
case _ => ;
}
isDismounting = None
}
}
}
object CarrierBehavior {
private val log = org.log4s.getLogger(name = "CarrierBehavior")
final case class CheckCargoMounting(cargo_guid: PlanetSideGUID, cargo_mountpoint: Int, iteration: Int)
final case class CheckCargoDismount(cargo_guid: PlanetSideGUID, cargo_mountpoint: Int, iteration: Int, bailed: Boolean)
/**
* na
* @param carrierGUID the ferrying carrier vehicle
* @param cargoGUID the vehicle being ferried as cargo
* @param carrier the ferrying carrier vehicle
* @param mountPoint the cargo hold to which the cargo vehicle is stowed
* @param iteration number of times a proper mounting for this combination has been queried
*/
def HandleCheckCargoMounting(
zone: Zone,
carrierGUID: PlanetSideGUID,
cargoGUID: PlanetSideGUID,
carrier: Vehicle,
mountPoint: Int,
iteration: Int
): Boolean = {
zone.GUID(cargoGUID) match {
case Some(cargo: Vehicle) =>
HandleCheckCargoMounting(cargoGUID, cargo, carrierGUID, carrier, mountPoint, iteration)
case cargo if iteration > 0 =>
log.warn(s"HandleCheckCargoMounting: participant vehicles changed in the middle of a mounting event")
LogCargoEventMissingVehicleError("HandleCheckCargoMounting: cargo", cargo, cargoGUID)
false
case _ =>
false
}
}
/**
* na
* @param cargoGUID the vehicle being ferried as cargo
* @param cargo the vehicle being ferried as cargo
* @param carrierGUID the ferrying carrier vehicle
* @param carrier the ferrying carrier vehicle
* @param mountPoint the cargo hold to which the cargo vehicle is stowed
* @param iteration number of times a proper mounting for this combination has been queried
*/
private def HandleCheckCargoMounting(
cargoGUID: PlanetSideGUID,
cargo: Vehicle,
carrierGUID: PlanetSideGUID,
carrier: Vehicle,
mountPoint: Int,
iteration: Int
): Boolean = {
val zone = carrier.Zone
val distance = Vector3.DistanceSquared(cargo.Position, carrier.Position)
carrier.CargoHold(mountPoint) match {
case Some(hold) if !hold.isOccupied && hold.canBeOccupiedBy(cargo) =>
log.debug(
s"HandleCheckCargoMounting: mount distance between $cargoGUID and $carrierGUID - actual=$distance, target=64"
)
if (distance <= 64) {
//cargo vehicle is close enough to assume to be physically within the carrier's hold; mount it
log.debug(s"HandleCheckCargoMounting: mounting cargo vehicle in carrier at distance of $distance")
hold.mount(cargo)
cargo.MountedIn = carrierGUID
cargo.Velocity = None
cargo.Actor ! CargoBehavior.EndCargoMounting(carrierGUID)
zone.VehicleEvents ! VehicleServiceMessage(
s"${cargo.Actor}",
VehicleAction.SendResponse(PlanetSideGUID(0), PlanetsideAttributeMessage(cargoGUID, 0, cargo.Health))
)
zone.VehicleEvents ! VehicleServiceMessage(
s"${cargo.Actor}",
VehicleAction.SendResponse(PlanetSideGUID(0), PlanetsideAttributeMessage(cargoGUID, 68, cargo.Shields))
)
CargoMountBehaviorForAll(carrier, cargo, mountPoint)
zone.actor ! ZoneActor.RemoveFromBlockMap(cargo)
false
} else if (distance > 625 || iteration >= 40) {
//vehicles moved too far away or took too long to get into proper position; abort mounting
log.debug(
"HandleCheckCargoMounting: cargo vehicle is too far away or didn't mount within allocated time - aborting"
)
cargo.Actor ! CargoBehavior.EndCargoMounting(carrierGUID)
val cargoDriverGUID = cargo.Seats(0).occupant.get.GUID
zone.VehicleEvents ! VehicleServiceMessage(
zone.id,
VehicleAction.SendResponse(
cargoDriverGUID,
CargoMountPointStatusMessage(
carrierGUID,
PlanetSideGUID(0),
PlanetSideGUID(0),
cargoGUID,
mountPoint,
CargoStatus.Empty,
0
)
)
)
false
//sending packet to the cargo vehicle's client results in player being lock in own vehicle
//player gets stuck as "always trying to remount the cargo hold"
//obviously, don't do this
} else {
//cargo vehicle still not in position but there is more time to wait; reschedule check
true
}
case None =>
log.warn(s"HandleCheckCargoMounting: carrier vehicle $carrier does not have a cargo hold #$mountPoint")
cargo.Actor ! CargoBehavior.EndCargoMounting(carrierGUID)
false
case _ =>
if (iteration == 0) {
log.warn(
s"HandleCheckCargoMounting: carrier vehicle $carrier already possesses cargo in hold #$mountPoint; this operation was initiated incorrectly"
)
} else {
log.error(
s"HandleCheckCargoMounting: something has attached to the carrier vehicle $carrier cargo of hold #$mountPoint while a cargo dismount event was ongoing; stopped at iteration $iteration / 40"
)
}
cargo.Actor ! CargoBehavior.EndCargoMounting(carrierGUID)
false
}
}
/**
* na
* @param cargoGUID na
* @param carrierGUID na
* @param mountPoint na
* @param iteration na
*/
def HandleCheckCargoDismounting(
zone: Zone,
carrierGUID: PlanetSideGUID,
cargoGUID: PlanetSideGUID,
carrier: Vehicle,
mountPoint: Int,
iteration: Int,
bailed: Boolean
): Boolean = {
zone.GUID(cargoGUID) match {
case Some(cargo: Vehicle) =>
HandleCheckCargoDismounting(cargoGUID, cargo, carrierGUID, carrier, mountPoint, iteration, bailed)
case cargo if iteration > 0 =>
log.error(s"HandleCheckCargoDismounting: participant vehicles changed in the middle of a mounting event")
LogCargoEventMissingVehicleError("HandleCheckCargoDismounting: carrier", cargo, cargoGUID)
false
case _ =>
false
}
}
/**
* na
* @param cargoGUID na
* @param cargo na
* @param carrierGUID na
* @param carrier na
* @param mountPoint na
* @param iteration na
*/
private def HandleCheckCargoDismounting(
cargoGUID: PlanetSideGUID,
cargo: Vehicle,
carrierGUID: PlanetSideGUID,
carrier: Vehicle,
mountPoint: Int,
iteration: Int,
bailed: Boolean
): Boolean = {
val zone = carrier.Zone
carrier.CargoHold(mountPoint) match {
case Some(hold) =>
val distance = Vector3.DistanceSquared(cargo.Position, carrier.Position)
log.debug(
s"HandleCheckCargoDismounting: mount distance between $cargoGUID and $carrierGUID - actual=$distance, target=225"
)
if ((bailed && iteration > 0) || distance > 225) {
//cargo vehicle has moved far enough away; close the carrier's hold door
log.debug(
s"HandleCheckCargoDismounting: dismount of cargo vehicle from carrier complete at distance of $distance"
)
cargo.Actor ! CargoBehavior.EndCargoDismounting(carrierGUID)
val cargoDriverGUID = cargo.Seats(0).occupant.get.GUID
zone.VehicleEvents ! VehicleServiceMessage(
zone.id,
VehicleAction.SendResponse(
cargoDriverGUID,
CargoMountPointStatusMessage(
carrierGUID,
PlanetSideGUID(0),
PlanetSideGUID(0),
cargoGUID,
mountPoint,
CargoStatus.Empty,
0
)
)
)
false
//sending packet to the cargo vehicle's client results in player being lock in own vehicle
//player gets stuck as "always trying to remount the cargo hold"
//obviously, don't do this
} else if (iteration > 40) {
//cargo vehicle has spent too long not getting far enough away; restore the cargo's mount in the carrier hold
hold.mount(cargo)
cargo.MountedIn = carrierGUID
cargo.Actor ! CargoBehavior.EndCargoMounting(carrierGUID)
CargoMountBehaviorForAll(carrier, cargo, mountPoint)
zone.actor ! ZoneActor.RemoveFromBlockMap(cargo)
false
} else {
//cargo vehicle did not move far away enough yet and there is more time to wait; reschedule check
true
}
case None =>
log.warn(s"HandleCheckCargoDismounting: carrier vehicle $carrier does not have a cargo hold #$mountPoint")
cargo.Actor ! CargoBehavior.EndCargoDismounting(carrierGUID)
false
case _ =>
if (iteration == 0) {
log.warn(
s"HandleCheckCargoDismounting: carrier vehicle $carrier will not discharge the cargo of hold #$mountPoint; this operation was initiated incorrectly"
)
} else {
log.error(
s"HandleCheckCargoDismounting: something has attached to the carrier vehicle $carrier cargo of hold #$mountPoint while a cargo dismount event was ongoing; stopped at iteration $iteration / 40"
)
}
cargo.Actor ! CargoBehavior.EndCargoDismounting(carrierGUID)
false
}
}
/**
* na
* @param zone na
* @param cargo_guid na
* @param bailed na
* @param requestedByPassenger na
* @param kicked na
*/
def HandleVehicleCargoDismount(
zone: Zone,
cargo_guid: PlanetSideGUID,
bailed: Boolean,
requestedByPassenger: Boolean,
kicked: Boolean
): Boolean = {
zone.GUID(cargo_guid) match {
case Some(cargo: Vehicle) =>
zone.GUID(cargo.MountedIn) match {
case Some(ferry: Vehicle) =>
HandleVehicleCargoDismount(cargo_guid, cargo, ferry.GUID, ferry, bailed, requestedByPassenger, kicked)
case _ =>
log.warn(
s"DismountVehicleCargo: target ${cargo.Definition.Name}@$cargo_guid does not know what treats it as cargo"
)
false
}
case _ =>
log.warn(s"DismountVehicleCargo: target $cargo_guid either is not a vehicle in ${zone.id} or does not exist")
false
}
}
/**
* na
* @param cargoGUID the globally unique number for the vehicle being ferried
* @param cargo the vehicle being ferried
* @param carrierGUID the globally unique number for the vehicle doing the ferrying
* @param carrier the vehicle doing the ferrying
* @param bailed the ferried vehicle is bailing from the cargo hold
* @param requestedByPassenger the ferried vehicle is being politely disembarked from the cargo hold
* @param kicked the ferried vehicle is being kicked out of the cargo hold
*/
def HandleVehicleCargoDismount(
cargoGUID: PlanetSideGUID,
cargo: Vehicle,
carrierGUID: PlanetSideGUID,
carrier: Vehicle,
bailed: Boolean,
requestedByPassenger: Boolean,
kicked: Boolean
): Boolean = {
val zone = carrier.Zone
carrier.CargoHolds.find({ case (_, hold) => hold.occupant.contains(cargo) }) match {
case Some((mountPoint, hold)) =>
cargo.MountedIn = None
hold.unmount(
cargo,
if (bailed) BailType.Bailed else if (kicked) BailType.Kicked else BailType.Normal
)
val driverOpt = cargo.Seats(0).occupant
val rotation: Vector3 = if (Vehicles.CargoOrientation(cargo) == 1) { //TODO: BFRs will likely also need this set
//dismount router "sideways" from the lodestar
carrier.Orientation.xy + Vector3.z((carrier.Orientation.z - 90) % 360)
} else {
carrier.Orientation
}
val cargoHoldPosition: Vector3 = if (carrier.Definition == GlobalDefinitions.dropship) {
//the galaxy cargo bay is offset backwards from the center of the vehicle
carrier.Position + Vector3.Rz(Vector3(0, -7, 0), math.toRadians(carrier.Orientation.z))
} else {
//the lodestar's cargo hold is almost the center of the vehicle
carrier.Position
}
val GUID0 = Service.defaultPlayerGUID
val zoneId = zone.id
val events = zone.VehicleEvents
val cargoActor = cargo.Actor
events ! VehicleServiceMessage(
s"$cargoActor",
VehicleAction.SendResponse(GUID0, PlanetsideAttributeMessage(cargoGUID, 0, cargo.Health))
)
events ! VehicleServiceMessage(
s"$cargoActor",
VehicleAction.SendResponse(GUID0, PlanetsideAttributeMessage(cargoGUID, 68, cargo.Shields))
)
zone.actor ! ZoneActor.AddToBlockMap(cargo, carrier.Position)
if (carrier.isFlying) {
//the carrier vehicle is flying; eject the cargo vehicle
val ejectCargoMsg =
CargoMountPointStatusMessage(carrierGUID, GUID0, GUID0, cargoGUID, mountPoint, CargoStatus.InProgress, 0)
val detachCargoMsg = ObjectDetachMessage(carrierGUID, cargoGUID, cargoHoldPosition - Vector3.z(1), rotation)
val resetCargoMsg =
CargoMountPointStatusMessage(carrierGUID, GUID0, GUID0, cargoGUID, mountPoint, CargoStatus.Empty, 0)
events ! VehicleServiceMessage(zoneId, VehicleAction.SendResponse(GUID0, ejectCargoMsg))
events ! VehicleServiceMessage(zoneId, VehicleAction.SendResponse(GUID0, detachCargoMsg))
events ! VehicleServiceMessage(zoneId, VehicleAction.SendResponse(GUID0, resetCargoMsg))
log.debug(s"HandleVehicleCargoDismount: eject - $ejectCargoMsg, detach - $detachCargoMsg")
if (driverOpt.isEmpty) {
//TODO cargo should drop like a rock like normal; until then, deconstruct it
cargoActor ! Vehicle.Deconstruct()
}
false
} else {
//the carrier vehicle is not flying; just open the door and let the cargo vehicle back out; force it out if necessary
val cargoStatusMessage =
CargoMountPointStatusMessage(carrierGUID, GUID0, cargoGUID, GUID0, mountPoint, CargoStatus.InProgress, 0)
val cargoDetachMessage =
ObjectDetachMessage(carrierGUID, cargoGUID, cargoHoldPosition + Vector3.z(1f), rotation)
events ! VehicleServiceMessage(zoneId, VehicleAction.SendResponse(GUID0, cargoStatusMessage))
events ! VehicleServiceMessage(zoneId, VehicleAction.SendResponse(GUID0, cargoDetachMessage))
driverOpt match {
case Some(driver) =>
events ! VehicleServiceMessage(
s"${driver.Name}",
VehicleAction.KickCargo(GUID0, cargo, cargo.Definition.AutoPilotSpeed2, 2500)
)
case None =>
val resetCargoMsg =
CargoMountPointStatusMessage(carrierGUID, GUID0, GUID0, cargoGUID, mountPoint, CargoStatus.Empty, 0)
events ! VehicleServiceMessage(zoneId, VehicleAction.SendResponse(GUID0, resetCargoMsg)) //lazy
//TODO cargo should back out like normal; until then, deconstruct it
cargoActor ! Vehicle.Deconstruct()
}
true
}
case None =>
log.warn(s"HandleDismountVehicleCargo: can not locate cargo $cargo in any hold of the carrier vehicle $carrier")
false
}
}
//logging and messaging support functions
/**
* na
* @param decorator custom text for these messages in the log
* @param target an optional the target object
* @param targetGUID the expected globally unique identifier of the target object
*/
def LogCargoEventMissingVehicleError(
decorator: String,
target: Option[PlanetSideGameObject],
targetGUID: PlanetSideGUID
): Unit = {
target match {
case Some(_: Vehicle) => ;
case Some(_) => log.error(s"$decorator target $targetGUID no longer identifies as a vehicle")
case None => log.error(s"$decorator target $targetGUID has gone missing")
}
}
/**
* Produce an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
* that will set up a realized parent-child association between a ferrying vehicle and a ferried vehicle.
* @see `CargoMountPointStatusMessage`
* @see `ObjectAttachMessage`
* @see `Vehicles.CargoOrientation`
* @param carrier the ferrying vehicle
* @param cargo the ferried vehicle
* @param mountPoint the point on the ferryoing vehicle where the ferried vehicle is attached;
* also known as a "cargo hold"
* @return a tuple composed of an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
*/
def CargoMountMessages(
carrier: Vehicle,
cargo: Vehicle,
mountPoint: Int
): (ObjectAttachMessage, CargoMountPointStatusMessage) = {
CargoMountMessages(carrier.GUID, cargo.GUID, mountPoint, Vehicles.CargoOrientation(cargo))
}
/**
* Produce an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
* that will set up a realized parent-child association between a ferrying vehicle and a ferried vehicle.
* @see `CargoMountPointStatusMessage`
* @see `ObjectAttachMessage`
* @param carrierGUID the ferrying vehicle
* @param cargoGUID the ferried vehicle
* @param mountPoint the point on the ferryoing vehicle where the ferried vehicle is attached
* @param orientation the positioning of the cargo vehicle in the carrier cargo bay
* @return a tuple composed of an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
*/
def CargoMountMessages(
carrierGUID: PlanetSideGUID,
cargoGUID: PlanetSideGUID,
mountPoint: Int,
orientation: Int
): (ObjectAttachMessage, CargoMountPointStatusMessage) = {
(
ObjectAttachMessage(carrierGUID, cargoGUID, mountPoint),
CargoMountPointStatusMessage(
carrierGUID,
cargoGUID,
cargoGUID,
PlanetSideGUID(0),
mountPoint,
CargoStatus.Occupied,
orientation
)
)
}
/**
* Dispatch an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet to all other clients, not this one.
* @see `CargoMountPointStatusMessage`
* @see `ObjectAttachMessage`
* @param carrier the ferrying vehicle
* @param cargo the ferried vehicle
* @param mountPoint the point on the ferryoing vehicle where the ferried vehicle is attached
* @return a tuple composed of an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
*/
def CargoMountBehaviorForOthers(
carrier: Vehicle,
cargo: Vehicle,
mountPoint: Int,
exclude: PlanetSideGUID
): (ObjectAttachMessage, CargoMountPointStatusMessage) = {
val msgs @ (attachMessage, mountPointStatusMessage) = CargoMountMessages(carrier, cargo, mountPoint)
CargoMountMessagesForOthers(carrier.Zone, exclude, attachMessage, mountPointStatusMessage)
msgs
}
/**
* Dispatch an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet to all other clients, not this one.
* @see `CargoMountPointStatusMessage`
* @see `ObjectAttachMessage`
* @param attachMessage an `ObjectAttachMessage` packet suitable for initializing cargo operations
* @param mountPointStatusMessage a `CargoMountPointStatusMessage` packet suitable for initializing cargo operations
*/
def CargoMountMessagesForOthers(
zone: Zone,
exclude: PlanetSideGUID,
attachMessage: ObjectAttachMessage,
mountPointStatusMessage: CargoMountPointStatusMessage
): Unit = {
zone.VehicleEvents ! VehicleServiceMessage(zone.id, VehicleAction.SendResponse(exclude, attachMessage))
zone.VehicleEvents ! VehicleServiceMessage(zone.id, VehicleAction.SendResponse(exclude, mountPointStatusMessage))
}
/**
* Dispatch an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet to everyone.
* @see `CargoMountPointStatusMessage`
* @see `ObjectAttachMessage`
* @param carrier the ferrying vehicle
* @param cargo the ferried vehicle
* @param mountPoint the point on the ferryoing vehicle where the ferried vehicle is attached
* @return a tuple composed of an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
*/
def CargoMountBehaviorForAll(
carrier: Vehicle,
cargo: Vehicle,
mountPoint: Int
): (ObjectAttachMessage, CargoMountPointStatusMessage) = {
val zone = carrier.Zone
val zoneId = zone.id
val msgs @ (attachMessage, mountPointStatusMessage) = CargoMountMessages(carrier, cargo, mountPoint)
zone.VehicleEvents ! VehicleServiceMessage(
zoneId,
VehicleAction.SendResponse(Service.defaultPlayerGUID, attachMessage)
)
zone.VehicleEvents ! VehicleServiceMessage(
zoneId,
VehicleAction.SendResponse(Service.defaultPlayerGUID, mountPointStatusMessage)
)
msgs
}
}

View file

@ -3,7 +3,7 @@ package net.psforever.objects.vehicles.control
import net.psforever.objects._
import net.psforever.objects.serverobject.damage.{Damageable, DamageableVehicle}
import net.psforever.objects.vehicles.CargoBehavior
import net.psforever.objects.vehicles.{Cargo, CarrierBehavior}
import net.psforever.objects.vital.interaction.DamageResult
/**
@ -13,10 +13,15 @@ import net.psforever.objects.vital.interaction.DamageResult
*/
class CargoCarrierControl(vehicle: Vehicle)
extends VehicleControl(vehicle)
with CargoBehavior {
def CargoObject = vehicle
with CarrierBehavior {
def CarrierObject = vehicle
override def commonEnabledBehavior: Receive = super.commonEnabledBehavior.orElse(cargoBehavior)
override def postStop() : Unit = {
super.postStop()
endAllCarrierOperations()
}
override def commonEnabledBehavior: Receive = super.commonEnabledBehavior.orElse(carrierBehavior)
/**
* If the vehicle becomes disabled, the safety and autonomy of the cargo should be prioritized.
@ -24,21 +29,12 @@ class CargoCarrierControl(vehicle: Vehicle)
*/
override def PrepareForDisabled(kickPassengers: Boolean) : Unit = {
//abandon all cargo
vehicle.CargoHolds.values
.collect {
case hold if hold.isOccupied =>
val cargo = hold.occupant.get
CargoBehavior.HandleVehicleCargoDismount(
cargo.GUID,
cargo,
vehicle.GUID,
vehicle,
bailed = false,
requestedByPassenger = false,
kicked = false
)
}
super.PrepareForDisabled(kickPassengers)
vehicle.CargoHolds.collect {
case (index, hold : Cargo) if hold.isOccupied =>
val cargo = hold.occupant.get
checkCargoDismount(cargo.GUID, index, iteration = 0, bailed = false)
super.PrepareForDisabled(kickPassengers)
}
}
/**

View file

@ -34,7 +34,7 @@ class DeployingVehicleControl(vehicle: Vehicle)
case msg : Deployment.TryUndeploy =>
deployBehavior.apply(msg)
case msg @ Mountable.TryDismount(_, seat_num) =>
case msg @ Mountable.TryDismount(_, seat_num, _) =>
dismountBehavior.apply(msg)
dismountCleanup(seat_num)
}

View file

@ -18,7 +18,7 @@ import net.psforever.objects.serverobject.hackable.GenericHackables
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
import net.psforever.objects.serverobject.repair.RepairableVehicle
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.vehicles.{AccessPermissionGroup, CargoBehavior, Utility, VehicleLockState}
import net.psforever.objects.vehicles._
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
import net.psforever.objects.vital.VehicleShieldCharge
import net.psforever.objects.vital.environment.EnvironmentReason
@ -51,7 +51,8 @@ class VehicleControl(vehicle: Vehicle)
with JammableMountedWeapons
with ContainableBehavior
with AggravatedBehavior
with RespondsToZoneEnvironment {
with RespondsToZoneEnvironment
with CargoBehavior {
//make control actors belonging to utilities when making control actor belonging to vehicle
vehicle.Utilities.foreach { case (_, util) => util.Setup }
@ -68,6 +69,9 @@ class VehicleControl(vehicle: Vehicle)
def ContainerObject = vehicle
def InteractiveObject = vehicle
def CargoObject = vehicle
SetInteraction(EnvironmentAttribute.Water, doInteractingWithWater)
SetInteraction(EnvironmentAttribute.Lava, doInteractingWithLava)
SetInteraction(EnvironmentAttribute.Death, doInteractingWithDeath)
@ -94,6 +98,7 @@ class VehicleControl(vehicle: Vehicle)
util().Actor = Default.Actor
}
recoverFromEnvironmentInteracting()
endAllCargoOperations()
}
def commonEnabledBehavior: Receive = checkBehavior
@ -102,6 +107,7 @@ class VehicleControl(vehicle: Vehicle)
.orElse(canBeRepairedByNanoDispenser)
.orElse(containerBehavior)
.orElse(environmentBehavior)
.orElse(cargoBehavior)
.orElse {
case Vehicle.Ownership(None) =>
LoseOwnership()
@ -113,7 +119,7 @@ class VehicleControl(vehicle: Vehicle)
mountBehavior.apply(msg)
mountCleanup(mount_point, player)
case msg @ Mountable.TryDismount(_, seat_num) =>
case msg @ Mountable.TryDismount(_, seat_num, _) =>
dismountBehavior.apply(msg)
dismountCleanup(seat_num)
@ -247,7 +253,7 @@ class VehicleControl(vehicle: Vehicle)
def commonDisabledBehavior: Receive = checkBehavior
.orElse {
case msg @ Mountable.TryDismount(_, seat_num) =>
case msg @ Mountable.TryDismount(_, seat_num, _) =>
dismountBehavior.apply(msg)
dismountCleanup(seat_num)
@ -372,13 +378,7 @@ class VehicleControl(vehicle: Vehicle)
//escape being someone else's cargo
vehicle.MountedIn match {
case Some(_) =>
CargoBehavior.HandleVehicleCargoDismount(
zone,
guid,
bailed = false,
requestedByPassenger = false,
kicked = false
)
startCargoDismounting(bailed = false)
case _ => ;
}
if (!vehicle.isFlying || kickPassengers) {
@ -386,13 +386,13 @@ class VehicleControl(vehicle: Vehicle)
vehicle.Seats.values.foreach { seat =>
seat.occupant match {
case Some(player) =>
seat.unmount(player)
seat.unmount(player, BailType.Kicked)
player.VehicleSeated = None
if (player.isAlive) {
zone.actor ! ZoneActor.AddToBlockMap(player, vehicle.Position)
}
if (player.HasGUID) {
events ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(player.GUID, 4, false, guid))
events ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(player.GUID, 4, true, guid))
}
case None => ;
}

View file

@ -0,0 +1,78 @@
//Copyright (c) 2021 PSForever
package net.psforever.objects.vital
class CollisionData() {
var xy: CollisionXYData = CollisionXYData()
var z: CollisionZData = CollisionZData()
}
class ExosuitCollisionData() {
var forceFactor: Float = 1f
var massFactor: Float = 1f
}
class AdvancedCollisionData() extends CollisionData() {
var avatarCollisionDamageMax: Int = Int.MaxValue
//I don't know what to do with these, so they will go here for now
var minHp: Float = 1f
var maxHp: Float = 10f
var minForce: Float = 15f
var maxForce: Float = 50f
}
trait CollisionDoesDamage {
def hp(): List[Int]
def hp(d: Int): Int = {
val _hp = hp()
_hp.lift(d) match {
case Some(n) => n
case None => _hp.last
}
}
}
final case class CollisionZData(data: Iterable[(Float, Int)])
extends CollisionDoesDamage {
assert(data.nonEmpty, "some collision data must be defined")
def height(): List[Float] = data.unzip._1.toList
override def hp(): List[Int] = data.unzip._2.toList
def height(z: Float): Int = {
val n = data.toArray.indexWhere { case (h, _) => h > z }
if (n == -1) {
data.size - 1
} else {
n
}
}
}
object CollisionZData {
def apply(): CollisionZData = CollisionZData(Array((0f,0)))
}
final case class CollisionXYData(data: Iterable[(Float, Int)])
extends CollisionDoesDamage {
assert(data.nonEmpty, "some collision data must be defined")
def throttle(): List[Float] = data.unzip._1.toList
override def hp(): List[Int] = data.unzip._2.toList
def throttle(z: Float): Int = {
val n = data.toArray.indexWhere { case (h, _) => h > z }
if (n == -1) {
data.size - 1
} else {
n
}
}
}
object CollisionXYData {
def apply(): CollisionXYData = CollisionXYData(Array((0f,0)))
}

View file

@ -160,7 +160,7 @@ trait VitalityDefinition extends DamageModifiers {
var explodes: Boolean = false
/**
* damage that is inherent to the object, used for explosions and collisions, mainly
* damage that is inherent to the object, used for explosions, mainly
*/
var innateDamage: Option[DamageWithPosition] = None
@ -168,4 +168,8 @@ trait VitalityDefinition extends DamageModifiers {
innateDamage = Some(combustion)
innateDamage
}
val collision: CollisionData = new CollisionData()
var mass: Float = 1f
}

View file

@ -30,6 +30,7 @@ object DamageResolution extends Enumeration {
AggravatedSplashBurn, //continuous splashed aggravated damage
Explosion, //area of effect damage caused by an internal mechanism; unrelated to Splash
Environmental, //died to environmental causes
Suicide //i don't want to be the one the battles always choose
Suicide, //i don't want to be the one the battles always choose
Collision //went splat
= Value
}

View file

@ -0,0 +1,132 @@
// Copyright (c) 2021 PSForever
package net.psforever.objects.vital.collision
import net.psforever.objects.ballistics.{DeployableSource, PlayerSource, VehicleSource}
import net.psforever.objects.definition.ExoSuitDefinition
import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.types.Vector3
/**
* Falling damage is a product of the falling distance.
*/
case object GroundImpact extends CollisionDamageModifiers.Mod {
def calculate(damage: Int, data: DamageInteraction, cause: CollisionReason): Int =
CollisionDamageModifierFunctions.calculateGroundImpact(damage, data, cause)
}
/**
* Falling damage is a product of the falling distance.
*/
case object GroundImpactWith extends CollisionWithDamageModifiers.Mod {
def calculate(damage: Int, data: DamageInteraction, cause: CollisionWithReason): Int =
CollisionDamageModifierFunctions.calculateGroundImpact(damage, data, cause)
}
/**
* The damage of a lateral collision is a product of how fast one is reported moving at the time of impact.
* As per the format, moving velocity is translated into a throttle gear related to maximum forward speed.
* Driving at high velocity into an inelastic structure is bad for one's integrity.
*/
case object HeadonImpact extends CollisionDamageModifiers.Mod {
def calculate(damage: Int, data: DamageInteraction, cause: CollisionReason): Int = {
val vel = Vector3.Magnitude(cause.velocity.xy)
if (vel > 0.05f) {
val definition = data.target.Definition
val xy = definition.collision.xy
damage + xy.hp(xy.throttle((vel + 0.5f) / definition.maxForwardSpeed))
} else {
damage
}
}
}
/**
* The damage of a lateral collision is a product of how fast one is reported moving at the time of impact.
* Vehicles colliding with infantry is a special case as vehicles have a canned amount of damage just for that target.
* Deployables might be rigged for instant destruction the moment vehicles collide with them;
* in any case, check the deployable for damage handling.
* For all other targets, e.g., vehicles against other vehicles,
* damage is a function of the velocity turned into a percentage of full throttle matched against tiers of damage.
*/
case object HeadonImpactWithEntity extends CollisionWithDamageModifiers.Mod {
def calculate(damage: Int, data: DamageInteraction, cause: CollisionWithReason): Int = {
val vel = Vector3.Magnitude(cause.velocity.xy)
(data.target, cause.collidedWith) match {
case (p: PlayerSource, v: VehicleSource) =>
//player hit by vehicle; compromise between momentum-force-damage and velocity-damage
//the math here isn't perfect; 3D vector-velocity is being used in 1D momentum equations
val definition = v.Definition
val suit = ExoSuitDefinition.Select(p.ExoSuit, p.Faction).collision
val pmass = suit.massFactor
val pForceFactor = suit.forceFactor
val vmass = definition.mass
val maxAvtrDam = definition.collision.avatarCollisionDamageMax
val maxForwardSpeed = definition.maxForwardSpeed
val collisionTime = 1.5f //a drawn-out inelastic collision
val pvel = p.Velocity.getOrElse(Vector3.Zero)
val vvel = v.Velocity.getOrElse(Vector3.Zero)
val velCntrMass = (pvel * pmass + vvel * vmass) / (pmass + vmass) //velocity of the center of mass
val pvelFin = Vector3.neg(pvel - velCntrMass) + velCntrMass
val damp = math.min(pmass * Vector3.Magnitude(pvelFin - pvel) / (pForceFactor * collisionTime), maxAvtrDam.toFloat)
val dama = maxAvtrDam * 0.35f * (vel + 0.5f) / maxForwardSpeed
damage + (if (damp > dama) {
if (damp - dama > dama) {
damp - dama
} else {
dama
}
} else {
if (dama - damp > damp) {
dama - damp
} else {
damp
}
}).toInt
case (a: DeployableSource, b) =>
//deployable hit by vehicle; anything but an OHKO will cause visual desync, but still should calculate
val xy = a.Definition.collision.xy
damage + xy.hp(xy.throttle((vel + 0.5f) / b.Definition.maxForwardSpeed))
case (_, b: VehicleSource) if vel > 0.05f =>
//(usually) vehicle hit by another vehicle; exchange damages results
val xy = b.Definition.collision.xy
damage + xy.hp(xy.throttle((vel + 0.5f) / b.Definition.maxForwardSpeed))
case (a, _) if vel > 0.05f =>
//something hit by something
val xy = a.Definition.collision.xy
damage + xy.hp(xy.throttle((vel + 0.5f) / a.Definition.maxForwardSpeed))
case _ =>
//moving too slowly
damage
}
}
}
/**
* When the target collides with something,
* if the target is not faction related with the cause,
* the target takes multiplied damage.
* The tactical resonance area protection is identified by never moving (has no velocity).
*/
case class TrapCollisionDamageMultiplier(multiplier: Float) extends CollisionWithDamageModifiers.Mod {
def calculate(damage: Int, data: DamageInteraction, cause: CollisionWithReason): Int = {
val target = data.target
if (target.Velocity.nonEmpty && target.Faction != cause.collidedWith.Faction) {
(multiplier * damage).toInt
} else {
damage
}
}
}
object CollisionDamageModifierFunctions {
private[collision] def calculateGroundImpact(damage: Int, data: DamageInteraction, cause: CausedByColliding): Int = {
val fall = cause.fall
if (fall.toInt != 0) {
val z = data.target.Definition.collision.z
damage + z.hp(z.height(fall + 0.5f))
} else {
damage
}
}
}

View file

@ -0,0 +1,37 @@
// Copyright (c) 2021 PSForever
package net.psforever.objects.vital.collision
import net.psforever.objects.vital.base._
import net.psforever.objects.vital.interaction.DamageInteraction
object CollisionDamageModifiers {
/**
* For modifiers that should be used with `CollisionReason`.
*/
trait Mod extends DamageModifiers.Mod {
def calculate(damage: Int, data: DamageInteraction, cause: DamageReason): Int = {
cause match {
case o: CollisionReason => calculate(damage, data, o)
case _ => damage
}
}
def calculate(damage: Int, data: DamageInteraction, cause: CollisionReason): Int
}
}
object CollisionWithDamageModifiers {
/**
* or modifiers that should be used with `CollisionWithReason`.
*/
trait Mod extends DamageModifiers.Mod {
def calculate(damage: Int, data: DamageInteraction, cause: DamageReason): Int = {
cause match {
case o: CollisionWithReason => calculate(damage, data, o)
case _ => damage
}
}
def calculate(damage: Int, data: DamageInteraction, cause: CollisionWithReason): Int
}
}

View file

@ -1,38 +1,103 @@
// Copyright (c) 2020 PSForever
package net.psforever.objects.vital.collision
import net.psforever.objects.ballistics.SourceEntry
import net.psforever.objects.vital.base.{DamageReason, DamageResolution}
import net.psforever.objects.ballistics.{DeployableSource, SourceEntry, VehicleSource}
import net.psforever.objects.vital.base.{DamageModifiers, DamageReason, DamageResolution, DamageType}
import net.psforever.objects.vital.prop.DamageProperties
import net.psforever.objects.vital.resolution.DamageAndResistance
import net.psforever.types.Vector3
/**
* A wrapper for a "damage source" in damage calculations
* that parameterizes information necessary to explain a collision.
* Being "adversarial" requires that the damage be performed as an aggressive action between individuals.
* @param source na
* Common base for reporting damage for reasons of collisions.
*/
final case class AdversarialCollisionReason(source: DamageProperties) extends DamageReason {
def resolution: DamageResolution.Value = DamageResolution.Unresolved
trait CausedByColliding
extends DamageReason {
def resolution: DamageResolution.Value = DamageResolution.Collision
def same(test: DamageReason): Boolean = false
def source: DamageProperties = CollisionReason.noDamage
def damageModel: DamageAndResistance = null
def velocity: Vector3
override def adversary : Option[SourceEntry] = None
def fall: Float
}
/**
* A wrapper for a "damage source" in damage calculations
* that parameterizes information necessary to explain a collision.
* @param source na
* A wrapper for a "damage source" in damage calculations that explains a collision.
* @param velocity how fast the target is moving prior to the collision
* @param fall ongoing vertical displacement since before the collision
* @param damageModel the functionality that is necessary for interaction
* of a vital game object with the rest of the hostile game world
*/
final case class CollisionReason(source: DamageProperties) extends DamageReason {
def resolution: DamageResolution.Value = DamageResolution.Unresolved
final case class CollisionReason(
velocity: Vector3,
fall: Float,
damageModel: DamageAndResistance
) extends CausedByColliding {
def same(test: DamageReason): Boolean = test match {
case cr: CollisionReason => cr.velocity == velocity && math.abs(cr.fall - fall) < 0.05f
case _ => false
}
def same(test: DamageReason): Boolean = false
override def adversary: Option[SourceEntry] = None
def damageModel: DamageAndResistance = null
override def adversary : Option[SourceEntry] = None
override def unstructuredModifiers: List[DamageModifiers.Mod] = List(
GroundImpact,
HeadonImpact
)
}
/**
* A wrapper for a "damage source" in damage calculations that augment collision information
* by providing information about a qualified target that was struck.
* @param cause information about the collision
* @param collidedWith information regarding the qualified target that was struck
*/
final case class CollisionWithReason(
cause: CollisionReason,
collidedWith: SourceEntry
) extends CausedByColliding {
def same(test: DamageReason): Boolean = test match {
case cr: CollisionWithReason =>
cr.cause.same(cause) && cr.collidedWith == collidedWith
case _ =>
false
}
def velocity: Vector3 = cause.velocity
def fall: Float = cause.fall
def damageModel: DamageAndResistance = cause.damageModel
override def adversary: Option[SourceEntry] = {
collidedWith match {
case v: VehicleSource =>
v.occupants.head match {
case SourceEntry.None => Some(collidedWith)
case e => Some(e)
}
case d: DeployableSource =>
d.owner match {
case SourceEntry.None => Some(collidedWith)
case e => Some(e)
}
case _ =>
Some(collidedWith)
}
}
override def unstructuredModifiers: List[DamageModifiers.Mod] = List(
GroundImpactWith,
HeadonImpactWithEntity
) ++ collidedWith.Definition.Modifiers
override def attribution : Int = collidedWith.Definition.ObjectId
}
object CollisionReason {
/** The flags for calculating an absence of conventional damage for collision.
* Damage is considered `Direct`, however, which defines some resistance. */
val noDamage = new DamageProperties {
CausesDamageType = DamageType.Direct
}
}

View file

@ -5,6 +5,7 @@ import net.psforever.objects.serverobject.environment.PieceOfEnvironment
import net.psforever.objects.serverobject.structures.FoundationBuilder
import net.psforever.objects.serverobject.zipline.ZipLinePath
import net.psforever.objects.serverobject.{PlanetSideServerObject, ServerObjectBuilder}
import net.psforever.types.Vector3
/**
* The fixed instantiation and relation of a series of server objects.<br>
@ -129,4 +130,8 @@ class ZoneMap(val name: String) {
lattice = lattice ++ Set((source, target))
}
def areValidCoordinates(coordinates: Vector3): Boolean = {
coordinates.x >= 0 && coordinates.x <= scale.width &&
coordinates.y >= 0 && coordinates.y <= scale.height
}
}

View file

@ -230,7 +230,7 @@ class BlockMap(fullMapWidth: Int, fullMapHeight: Int, desiredSpanSize: Int) {
def move(target: BlockMapEntity, toPosition: Vector3): SectorPopulation = {
target.blockMapEntry match {
case Some(entry) => move(target, toPosition, entry.coords, entry.range)
case None => SectorGroup(Nil)
case _ => SectorGroup(Nil)
}
}

View file

@ -82,11 +82,15 @@ object BlockMapEntity {
* To properly update the range, perform a proper update.)
* @param target the entity on the blockmap
* @param newCoords the world coordinates of the entity, the position to which it is moving / being moved
* @return always `true`; we are updating this entry
* @return `true`, if we are updating this entry; `false`, otherwsie
*/
private def updateBlockMap(target: BlockMapEntity, newCoords: Vector3): Boolean = {
val oldEntry = target.blockMapEntry.get
target.blockMapEntry = Some(BlockMapEntry(newCoords, oldEntry.range, oldEntry.sectors))
true
target.blockMapEntry match {
case Some(oldEntry) =>
target.blockMapEntry = Some(BlockMapEntry(newCoords, oldEntry.range, oldEntry.sectors))
true
case None =>
false
}
}
}

View file

@ -33,11 +33,11 @@ object DamageWithPositionMessage extends Marshallable[DamageWithPositionMessage]
).xmap[DamageWithPositionMessage] (
{
case unk :: pos :: HNil =>
DamageWithPositionMessage(math.max(0, math.min(unk, 255)), pos)
DamageWithPositionMessage(unk, pos)
},
{
case DamageWithPositionMessage(unk, pos) =>
unk :: pos :: HNil
math.max(0, math.min(unk, 255)) :: pos :: HNil
}
)
}

View file

@ -7,12 +7,15 @@ import scodec.Codec
import scodec.codecs._
/**
* Note: For some reason this packet does not include the GUID of the vehicle that is being dismounted from. As a workaround vehicle.MountedIn in manually set/removed
* @param player_guid // GUID of the player that is rqeuesting dismount
* @param vehicle_guid GUID of the vehicle that is requesting dismount
* @param bailed If the vehicle bailed out of the cargo vehicle
* @param requestedByPassenger If a passenger of the vehicle in the cargo bay requests dismount this bit will be set
* @param kicked If the vehicle was kicked by the cargo vehicle pilot
* Request dismount of one vehicle (cargo) that is being ferried by another vehicle (carrier).
* The carrier has what is called a "cargo bay" which is where the cargo is being stored for ferrying.
* @param player_guid GUID of the player that is rqeuesting dismount;
* when kicked by carrier driver, player_guid will be PlanetSideGUID(0);
* when exiting of the cargo vehicle driver's own accord, player_guid will be the cargo vehicle driver
* @param vehicle_guid GUID of the vehicle that is requesting dismount (cargo)
* @param bailed if the cargo vehicle bailed out of the cargo vehicle
* @param requestedByPassenger if a passenger of the cargo vehicle requests dismount
* @param kicked if the cargo vehicle was kicked by the cargo vehicle pilot
*/
final case class DismountVehicleCargoMsg(
player_guid: PlanetSideGUID,

View file

@ -1,10 +1,27 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import enumeratum.values.{IntEnum, IntEnumEntry}
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import net.psforever.types.{PlanetSideGUID, Vector3}
import scodec.Attempt.Successful
import scodec.Codec
import scodec.codecs._
import shapeless.{::, HNil}
sealed abstract class CollisionIs(val value: Int) extends IntEnumEntry
object CollisionIs extends IntEnum[CollisionIs] {
val values = findValues
case object OfGroundVehicle extends CollisionIs(value = 0)
case object OfAircraft extends CollisionIs(value = 1)
case object OfInfantry extends CollisionIs(value = 2)
case object BetweenThings extends CollisionIs(value = 3) //currently, invalid
}
/**
* Dispatched by the client when the player has encountered a physical interaction that would cause damage.<br>
@ -13,7 +30,7 @@ import scodec.codecs._
* The first is the `player`, that is, the client's avatar.
* The second is the `target` with respect to the `player` - whatever the avatar ran into, or whatever ran into the avatar.
* In the case of isolated forms of collision such as fall damage the `target` fields are blank or zero'd.
* @param unk1 na
* @param collision_type a brief hint at the sort of interaction
* @param player the player or player-controlled vehicle
* @param target the other party in the collision
* @param player_health the player's health
@ -22,42 +39,56 @@ import scodec.codecs._
* @param target_velocity the target's velocity
* @param player_pos the player's world coordinates
* @param target_pos the target's world coordinates
* @param unk1 na
* @param unk2 na
* @param unk3 na
* @param unk4 na
*/
final case class GenericCollisionMsg(
unk1: Int,
player: PlanetSideGUID,
target: PlanetSideGUID,
player_health: Int,
target_health: Int,
player_velocity: Vector3,
target_velocity: Vector3,
player_pos: Vector3,
target_pos: Vector3,
unk2: Long,
unk3: Long,
unk4: Long
) extends PlanetSideGamePacket {
collision_type: CollisionIs,
player: PlanetSideGUID,
player_health: Int,
player_pos: Vector3,
player_velocity: Vector3,
target: PlanetSideGUID,
target_health: Int,
target_pos: Vector3,
target_velocity: Vector3,
unk1: Long,
unk2: Long,
unk3: Long
) extends PlanetSideGamePacket {
type Packet = GenericCollisionMsg
def opcode = GamePacketOpcode.GenericCollisionMsg
def encode = GenericCollisionMsg.encode(this)
}
object GenericCollisionMsg extends Marshallable[GenericCollisionMsg] {
private val collisionIsCodec: Codec[CollisionIs] = PacketHelpers.createIntEnumCodec(CollisionIs, uint2)
private val velocityFloatCodec: Codec[Vector3] = Vector3.codec_float
.narrow[Vector3](a => Successful(a * 3.6f), a => a * 0.2777778f) //this precision is necessary
implicit val codec: Codec[GenericCollisionMsg] = (
("unk1" | uint2) ::
("p" | PlanetSideGUID.codec) ::
("t" | PlanetSideGUID.codec) ::
("p_health" | uint16L) ::
("t_health" | uint16L) ::
("p_vel" | Vector3.codec_float) ::
("t_vel" | Vector3.codec_float) ::
("p_pos" | Vector3.codec_pos) ::
("t_pos" | Vector3.codec_pos) ::
("unk2" | uint32L) ::
("unk3" | uint32L) ::
("unk4" | uint32L)
).as[GenericCollisionMsg]
("collision_type" | collisionIsCodec) ::
("p" | PlanetSideGUID.codec) ::
("t" | PlanetSideGUID.codec) ::
("p_health" | uint16L) ::
("t_health" | uint16L) ::
("p_vel" | velocityFloatCodec) ::
("t_vel" | velocityFloatCodec) ::
("p_pos" | Vector3.codec_pos) ::
("t_pos" | Vector3.codec_pos) ::
("unk1" | uint32L) ::
("unk2" | uint32L) ::
("unk3" | uint32L)
).xmap[GenericCollisionMsg](
{
case ct :: p :: t :: ph :: th :: pv :: tv :: pp :: tp :: u1 :: u2 :: u3 :: HNil =>
GenericCollisionMsg(ct, p, ph, pp, pv, t, th, tp, tv, u1, u2, u3)
},
{
case GenericCollisionMsg(ct, p, ph, pp, pv, t, th, tp, tv, u1, u2, u3) =>
ct :: p :: t :: ph :: th :: pv :: tv :: pp :: tp :: u1 :: u2 :: u3 :: HNil
}
)
}

View file

@ -10,7 +10,7 @@ import scodec.codecs._
* @param player_guid The guid of the player sending the request to board another vehicle with a cargo vehicle
* @param vehicle_guid The guid of the vehicle for the requesting player
* @param target_vehicle The cargo vehicle guid e.g. Galaxy / Lodestar
* @param unk4
* @param unk4 na
*/
final case class MountVehicleCargoMsg(
player_guid: PlanetSideGUID,

View file

@ -20,7 +20,9 @@ import scodec.codecs._
* in repeating order: 13, 14, 10, 11, 12, 15;
* `None`, when landed and for all vehicles that do not fly
* @param unk3 na
* @param unk4 na
* @param unk4 na;
* 0 by default;
* for mosquito, can become various numbers during collision damage
* @param wheel_direction for ground vehicles, whether and how much the wheels are being turned;
* 15 for straight;
* 0 for hard right;
@ -80,15 +82,15 @@ object VehicleStateMessage extends Marshallable[VehicleStateMessage] {
implicit val codec: Codec[VehicleStateMessage] = (
("vehicle_guid" | PlanetSideGUID.codec) ::
("unk1" | uintL(3)) ::
("pos" | Vector3.codec_pos) ::
("ang" | codec_orient) ::
optional(bool, "vel" | Vector3.codec_vel) ::
optional(bool, "flying" | uintL(5)) ::
("unk3" | uintL(7)) ::
("unk4" | uint4L) ::
("wheel_direction" | uintL(5)) ::
("int5" | bool) ::
("is_cloaked" | bool)
("unk1" | uintL(bits = 3)) ::
("pos" | Vector3.codec_pos) ::
("ang" | codec_orient) ::
("vel" | optional(bool, Vector3.codec_vel)) ::
("flying" | optional(bool, uintL(bits = 5))) ::
("unk3" | uintL(bits = 7)) ::
("unk4" | uint4L) ::
("wheel_direction" | uintL(5)) ::
("is_decelerating" | bool) ::
("is_cloaked" | bool)
).as[VehicleStateMessage]
}

View file

@ -2,6 +2,7 @@
package net.psforever.types
import net.psforever.newcodecs._
import scodec.Attempt.Successful
import scodec.Codec
import scodec.codecs._
@ -38,6 +39,18 @@ final case class Vector3(x: Float, y: Float, z: Float) {
Vector3(x * scalar, y * scalar, z * scalar)
}
/**
* Operator for vector scaling, treating `Vector3` objects as actual mathematical vectors.
* The application of this overload is "vector / scalar" exclusively.
* "scalar / vector" is invalid.
* Due to rounding, may not be perfectly equivalent to "vector * ( 1 / scalar )".
* @param scalar the value to divide this vector
* @return a new `Vector3` object
*/
def /(scalar: Float): Vector3 = {
Vector3(x / scalar, y / scalar, z / scalar)
}
/**
* Operator for returning the ground-planar coordinates
* and ignoring the perpendicular distance from the world floor.
@ -100,7 +113,7 @@ object Vector3 {
("x" | newcodecs.q_float(-256.0, 256.0, 14)) ::
("y" | newcodecs.q_float(-256.0, 256.0, 14)) ::
("z" | newcodecs.q_float(-256.0, 256.0, 14))
).as[Vector3]
).as[Vector3].narrow(a => Successful(a * 3.6f), a => a * 0.2778f)
implicit val codec_float: Codec[Vector3] = (
("x" | floatL) ::

View file

@ -50,11 +50,11 @@ class CodecTest extends Specification {
val string_vel = hex"857D4E0FFFC0"
"decode" in {
Vector3.codec_vel.decode(string_vel.bits).require.value mustEqual Vector3(-3.84375f, 2.59375f, 255.96875f)
Vector3.codec_vel.decode(string_vel.bits).require.value mustEqual Vector3(-13.8375f, 9.3375f, 921.4875f)
}
"encode" in {
Vector3.codec_vel.encode(Vector3(-3.84375f, 2.59375f, 255.96875f)).require.bytes mustEqual string_vel
Vector3.codec_vel.encode(Vector3(-13.8375f, 9.3375f, 921.4875f)).require.bytes mustEqual string_vel
}
}
}

View file

@ -20,7 +20,7 @@ class FireHintMessageTest extends Specification {
u2 mustEqual 65399
u3 mustEqual 7581
u4 mustEqual 0
u5 mustEqual None
u5.isEmpty mustEqual true
case _ =>
ko
}
@ -34,7 +34,7 @@ class FireHintMessageTest extends Specification {
u2 mustEqual 65231
u3 mustEqual 64736
u4 mustEqual 3
u5 mustEqual Some(Vector3(21.5f, -6.8125f, 2.65625f))
u5.contains(Vector3(77.4f, -24.525f, 9.5625f)) mustEqual true
case _ =>
ko
}
@ -46,7 +46,7 @@ class FireHintMessageTest extends Specification {
pkt mustEqual string
}
"encode string2" in {
"encode (string2)" in {
val msg = FireHintMessage(
PlanetSideGUID(3592),
Vector3(2910.789f, 3744.875f, 69.0625f),
@ -54,7 +54,7 @@ class FireHintMessageTest extends Specification {
65231,
64736,
3,
Some(Vector3(21.5f, -6.8125f, 2.65625f))
Some(Vector3(77.4f, -24.525f, 9.5625f))
)
val pkt = PacketCoding.encodePacket(msg).require.toByteVector

View file

@ -11,17 +11,18 @@ class GenericCollisionMsgTest extends Specification {
//TODO find a better test later
val string =
hex"3C 92C00000190000001B2A8010932CEF505C70946F00000000000000000000000017725EBC6D6A058000000000000000000000000000003F8FF45140"
"decode" in {
PacketCoding.decodePacket(string).require match {
case GenericCollisionMsg(unk1, p, t, php, thp, pv, tv, ppos, tpos, unk2, unk3, unk4) =>
unk1 mustEqual 2
case GenericCollisionMsg(ct, p, php, ppos, pv, t, thp, tpos, tv, unk2, unk3, unk4) =>
ct mustEqual CollisionIs.OfInfantry
p mustEqual PlanetSideGUID(75)
t mustEqual PlanetSideGUID(0)
php mustEqual 100
thp mustEqual 0
pv.x mustEqual 32.166428f
pv.y mustEqual 23.712547f
pv.z mustEqual -0.012802706f
pv.x mustEqual 115.79913f
pv.y mustEqual 85.365166f
pv.z mustEqual -0.046089742f
tv.x mustEqual 0.0f
tv.z mustEqual 0.0f
tv.x mustEqual 0.0f
@ -38,22 +39,48 @@ class GenericCollisionMsgTest extends Specification {
ko
}
}
"encode" in {
val msg = GenericCollisionMsg(
2,
CollisionIs.OfInfantry,
PlanetSideGUID(75),
PlanetSideGUID(0),
100,
0,
Vector3(32.166428f, 23.712547f, -0.012802706f),
Vector3(0.0f, 0.0f, 0.0f),
Vector3(3986.7266f, 2615.3672f, 90.625f),
Vector3(115.79913f, 85.365166f, -0.046089742f),
PlanetSideGUID(0),
0,
Vector3(0.0f, 0.0f, 0.0f),
Vector3(0.0f, 0.0f, 0.0f),
0L,
0L,
1171341310L
)
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
pkt mustEqual string
//pkt mustEqual string
PacketCoding.decodePacket(pkt).require match {
case GenericCollisionMsg(ct, p, php, ppos, pv, t, thp, tpos, tv, unk2, unk3, unk4) =>
ct mustEqual CollisionIs.OfInfantry
p mustEqual PlanetSideGUID(75)
t mustEqual PlanetSideGUID(0)
php mustEqual 100
thp mustEqual 0
pv.x mustEqual 115.79913f
pv.y mustEqual 85.365166f
pv.z mustEqual -0.046089742f
tv.x mustEqual 0.0f
tv.z mustEqual 0.0f
tv.x mustEqual 0.0f
ppos.x mustEqual 3986.7266f
ppos.y mustEqual 2615.3672f
ppos.z mustEqual 90.625f
tpos.x mustEqual 0.0f
tpos.y mustEqual 0.0f
tpos.z mustEqual 0.0f
unk2 mustEqual 0L
unk3 mustEqual 0L
unk4 mustEqual 1171341310L
case _ =>
ko
}
}
}

View file

@ -15,7 +15,7 @@ class LongRangeProjectileInfoMessageTest extends Specification {
case LongRangeProjectileInfoMessage(guid, pos, vel) =>
guid mustEqual PlanetSideGUID(5330)
pos mustEqual Vector3(2264, 5115.039f, 31.046875f)
vel.contains(Vector3(-57.1875f, 9.875f, 47.5f)) mustEqual true
vel.contains(Vector3(-205.875f, 35.55f, 171.0f)) mustEqual true
case _ =>
ko
}
@ -25,7 +25,7 @@ class LongRangeProjectileInfoMessageTest extends Specification {
val msg = LongRangeProjectileInfoMessage(
PlanetSideGUID(5330),
Vector3(2264, 5115.039f, 31.046875f),
Vector3(-57.1875f, 9.875f, 47.5f)
Vector3(-205.875f, 35.55f, 171.0f)
)
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
pkt mustEqual string

View file

@ -99,8 +99,8 @@ class PlayerStateMessageTest extends Specification {
pos.y mustEqual 5987.6016f
pos.z mustEqual 44.1875f
vel.isDefined mustEqual true
vel.get.x mustEqual 2.53125f
vel.get.y mustEqual 6.5625f
vel.get.x mustEqual 9.1125f
vel.get.y mustEqual 23.625f
vel.get.z mustEqual 0.0f
facingYaw mustEqual 22.5f
facingPitch mustEqual -11.25f
@ -155,7 +155,7 @@ class PlayerStateMessageTest extends Specification {
val msg = PlayerStateMessage(
PlanetSideGUID(1696),
Vector3(4008.6016f, 5987.6016f, 44.1875f),
Some(Vector3(2.53125f, 6.5625f, 0f)),
Some(Vector3(9.1125f, 23.625f, 0f)),
22.5f,
-11.25f,
0f,

View file

@ -30,7 +30,7 @@ class PlayerStateMessageUpstreamTest extends Specification {
) =>
avatar_guid mustEqual PlanetSideGUID(75)
pos mustEqual Vector3(3694.1094f, 2735.4531f, 90.84375f)
vel.contains(Vector3(4.375f, 2.59375f, 0.0f)) mustEqual true
vel.contains(Vector3(15.75f, 9.3375f, 0.0f)) mustEqual true
facingYaw mustEqual 61.875f
facingPitch mustEqual -8.4375f
facingYawUpper mustEqual 0.0f
@ -51,7 +51,7 @@ class PlayerStateMessageUpstreamTest extends Specification {
val msg = PlayerStateMessageUpstream(
PlanetSideGUID(75),
Vector3(3694.1094f, 2735.4531f, 90.84375f),
Some(Vector3(4.375f, 2.59375f, 0.0f)),
Some(Vector3(15.75f, 9.3375f, 0.0f)),
61.875f,
-8.4375f,
0.0f,

View file

@ -49,9 +49,9 @@ class PlayerStateShiftMessageTest extends Specification {
state.get.pos.z mustEqual 50.3125f
state.get.viewYawLim mustEqual 50.625f
state.get.vel.isDefined mustEqual true
state.get.vel.get.x mustEqual 2.8125f
state.get.vel.get.y mustEqual -8.0f
state.get.vel.get.z mustEqual 0.375f
state.get.vel.get.x mustEqual 10.125f
state.get.vel.get.y mustEqual -28.8f
state.get.vel.get.z mustEqual 1.3499999f
unk.isDefined mustEqual false
case _ =>
ko
@ -74,7 +74,7 @@ class PlayerStateShiftMessageTest extends Specification {
"encode (pos and vel)" in {
val msg = PlayerStateShiftMessage(
ShiftState(2, Vector3(4645.75f, 5811.6016f, 50.3125f), 50.625f, Vector3(2.8125f, -8.0f, 0.375f))
ShiftState(2, Vector3(4645.75f, 5811.6016f, 50.3125f), 50.625f, Vector3(10.125f, -28.8f, 1.3499999f))
)
val pkt = PacketCoding.encodePacket(msg).require.toByteVector

View file

@ -21,10 +21,7 @@ class SplashHitMessageTest extends Specification {
projectile_pos.z mustEqual 90.921875f
unk2 mustEqual 0
unk3 mustEqual 0
projectile_vel.isDefined mustEqual true
projectile_vel.get.x mustEqual 2.21875f
projectile_vel.get.y mustEqual 0.90625f
projectile_vel.get.z mustEqual -1.125f
projectile_vel.contains(Vector3(7.9874997f, 3.2624998f, -4.0499997f)) mustEqual true
unk4.isDefined mustEqual false
targets.size mustEqual 2
//0
@ -53,7 +50,7 @@ class SplashHitMessageTest extends Specification {
Vector3(3681.3438f, 2728.9062f, 90.921875f),
0,
0,
Some(Vector3(2.21875f, 0.90625f, -1.125f)),
Some(Vector3(7.9874997f, 3.2624998f, -4.0499997f)),
None,
SplashedTarget(PlanetSideGUID(75), Vector3(3674.8438f, 2726.789f, 91.15625f), 286326784L, None) ::
SplashedTarget(PlanetSideGUID(372), Vector3(3679.1328f, 2722.6016f, 92.765625f), 268435456L, None) ::

View file

@ -24,7 +24,7 @@ class VehicleStateMessageTest extends Specification {
vel.isDefined mustEqual true
vel.get.x mustEqual 0.0f
vel.get.y mustEqual 0.0f
vel.get.z mustEqual 0.03125f
vel.get.z mustEqual 0.1125f
unk2.isDefined mustEqual false
unk3 mustEqual 0
unk4 mustEqual 0
@ -42,7 +42,7 @@ class VehicleStateMessageTest extends Specification {
0,
Vector3(3674.8438f, 2726.789f, 91.09375f),
Vector3(359.29688f, 1.0546875f, 90.35156f),
Some(Vector3(0.0f, 0.0f, 0.03125f)),
Some(Vector3(0.0f, 0.0f, 0.1125f)),
None,
0,
0,

View file

@ -18,7 +18,7 @@ class VehicleSubStateMessageTest extends Specification {
vehicle_pos mustEqual Vector3(3465.9575f, 2873.3635f, 91.05253f)
vehicle_ang mustEqual Vector3(11.6015625f, 0.0f, 3.515625f)
vel.isDefined mustEqual true
vel.get mustEqual Vector3(-0.40625f, 0.03125f, -0.8125f)
vel.contains(Vector3(-1.4625f, 0.1125f, -2.925f)) mustEqual true
unk1 mustEqual false
unk2.isDefined mustEqual false
case _ =>
@ -32,7 +32,7 @@ class VehicleSubStateMessageTest extends Specification {
PlanetSideGUID(3376),
Vector3(3465.9575f, 2873.3635f, 91.05253f),
Vector3(11.6015625f, 0.0f, 3.515625f),
Some(Vector3(-0.40625f, 0.03125f, -0.8125f)),
Some(Vector3(-1.4625f, 0.1125f, -2.925f)),
false,
None
)

View file

@ -34,7 +34,7 @@ class CharacterDataTest extends Specification {
pos.coord mustEqual Vector3(3674.8438f, 2726.789f, 91.15625f)
pos.orient mustEqual Vector3(0f, 0f, 64.6875f)
pos.vel.isDefined mustEqual true
pos.vel.get mustEqual Vector3(1.4375f, -0.4375f, 0f)
pos.vel.get mustEqual Vector3(5.1749997f, -1.5749999f, 0.0f)
basic match {
case CharacterAppearanceData(a, b, ribbons) =>
@ -294,7 +294,7 @@ class CharacterDataTest extends Specification {
val pos: PlacementData = PlacementData(
Vector3(3674.8438f, 2726.789f, 91.15625f),
Vector3(0f, 0f, 64.6875f),
Some(Vector3(1.4375f, -0.4375f, 0f))
Some(Vector3(5.1749997f, -1.5749999f, 0.0f))
)
val a: Int => CharacterAppearanceA = CharacterAppearanceA(
BasicCharacterData(

View file

@ -28,7 +28,7 @@ class MountedVehiclesTest extends Specification {
case vdata: VehicleData =>
vdata.pos.coord mustEqual Vector3(4571.6875f, 5602.1875f, 93)
vdata.pos.orient mustEqual Vector3(11.25f, 2.8125f, 92.8125f)
vdata.pos.vel.contains(Vector3(31.71875f, 8.875f, -0.03125f)) mustEqual true
vdata.pos.vel.contains(Vector3(114.1875f, 31.949999f, -0.1125f)) mustEqual true
vdata.data.faction mustEqual PlanetSideEmpire.TR
vdata.data.bops mustEqual false
vdata.data.alternate mustEqual false
@ -264,7 +264,7 @@ class MountedVehiclesTest extends Specification {
PlacementData(
Vector3(4571.6875f, 5602.1875f, 93),
Vector3(11.25f, 2.8125f, 92.8125f),
Some(Vector3(31.71875f, 8.875f, -0.03125f))
Some(Vector3(114.1875f, 31.949999f, -0.1125f))
),
CommonFieldData(PlanetSideEmpire.TR, false, false, false, None, false, Some(false), None, PlanetSideGUID(3776)),
false,

View file

@ -1061,7 +1061,7 @@ class DamageableWeaponTurretDestructionTest extends ActorTest {
assert(!turret.Destroyed)
turret.Actor ! Vitality.Damage(applyDamageToA) //also test destruction while jammered
vehicleProbe.receiveN(2, 1000 milliseconds) //flush jammered messages (see above)
vehicleProbe.receiveN(2, 1000 milliseconds) //flush jammered messages (see above)
assert(turret.Health > turret.Definition.DamageDestroysAt)
assert(turret.Jammed)
assert(!turret.Destroyed)
@ -1071,44 +1071,37 @@ class DamageableWeaponTurretDestructionTest extends ActorTest {
player1Probe.expectNoMessage(500 milliseconds)
val msg3 = player2Probe.receiveOne(200 milliseconds)
val msg56 = vehicleProbe.receiveN(2, 200 milliseconds)
assert(
msg12_4.head match {
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
case _ => false
}
)
assert(
msg12_4(1) match {
case AvatarServiceMessage("test", AvatarAction.Destroy(PlanetSideGUID(2), _, _, Vector3(1, 0, 0))) => true
case _ => false
}
)
assert(
msg3 match {
case Player.Die(_) => true
case _ => false
}
)
assert(
msg12_4(2) match {
case AvatarServiceMessage("test", AvatarAction.ObjectDelete(PlanetSideGUID(0), PlanetSideGUID(5), _)) => true
case _ => false
}
)
assert(
msg56.head match {
case VehicleServiceMessage.TurretUpgrade(SupportActor.ClearSpecific(List(t), _)) if turret eq t => true
case _ => false
}
)
assert(
msg56(1) match {
case VehicleServiceMessage.TurretUpgrade(TurretUpgrader.AddTask(t, _, TurretUpgrade.None, _))
if t eq turret =>
true
case _ => false
}
)
msg12_4.head match {
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => ;
case _ =>
assert(false, s"DamageableWeaponTurretDestructionTest-1: ${msg12_4.head}")
}
msg12_4(1) match {
case AvatarServiceMessage("test", AvatarAction.Destroy(PlanetSideGUID(2), _, _, Vector3(1, 0, 0))) => ;
case _ =>
assert(false, s"DamageableWeaponTurretDestructionTest-2: ${msg12_4(1)}")
}
msg3 match {
case Player.Die(_) => true
case _ =>
assert(false, s"DamageableWeaponTurretDestructionTest-3: player not dead - $msg3")
}
msg12_4(2) match {
case AvatarServiceMessage("test", AvatarAction.ObjectDelete(PlanetSideGUID(0), PlanetSideGUID(5), _)) => ;
case _ =>
assert(false, s"DamageableWeaponTurretDestructionTest-4: ${msg12_4(2)}")
}
msg56.head match {
case VehicleServiceMessage.TurretUpgrade(SupportActor.ClearSpecific(List(t), _)) if turret eq t => ;
case _ =>
assert(false, s"DamageableWeaponTurretDestructionTest-5: ${msg56.head}")
}
msg56(1) match {
case VehicleServiceMessage.TurretUpgrade(TurretUpgrader.AddTask(t, _, TurretUpgrade.None, _)) if t eq turret => ;
true
case _ =>
assert(false, s"DamageableWeaponTurretDestructionTest-6: ${msg56(1)}")
}
assert(turret.Health <= turret.Definition.DamageDestroysAt)
assert(!turret.Jammed)
assert(turret.Destroyed)

View file

@ -14,7 +14,7 @@ import net.psforever.objects.guid.source.MaxNumberSource
import net.psforever.objects.serverobject.environment._
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.vehicles.VehicleLockState
import net.psforever.objects.vehicles.control.VehicleControl
import net.psforever.objects.vehicles.control.{CargoCarrierControl, VehicleControl}
import net.psforever.objects.vital.{VehicleShieldCharge, Vitality}
import net.psforever.objects.zones.{Zone, ZoneMap}
import net.psforever.packet.game._
@ -66,16 +66,11 @@ class VehicleControlPrepareForDeletionPassengerTest extends ActorTest {
vehicle.Actor ! Vehicle.Deconstruct()
val vehicle_msg = vehicleProbe.receiveN(1, 500 milliseconds)
assert(
vehicle_msg.head match {
case VehicleServiceMessage(
"test",
VehicleAction.KickPassenger(PlanetSideGUID(2), 4, false, PlanetSideGUID(1))
) =>
true
case _ => false
}
)
vehicle_msg.head match {
case VehicleServiceMessage("test", VehicleAction.KickPassenger(PlanetSideGUID(2), 4, true, PlanetSideGUID(1))) => ;
case _ =>
assert(false, s"VehicleControlPrepareForDeletionPassengerTest: ${vehicle_msg.head}")
}
assert(player1.VehicleSeated.isEmpty)
assert(vehicle.Seats(1).occupant.isEmpty)
}
@ -96,19 +91,22 @@ class VehicleControlPrepareForDeletionMountedInTest extends FreedContextActorTes
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
vehicle.Faction = PlanetSideEmpire.TR
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test-cargo")
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)
player1.GUID = PlanetSideGUID(3)
var utilityId = 10
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 =>
util().GUID = PlanetSideGUID(utilityId)
guid.register(util(), utilityId)
utilityId += 1
}
vehicle.Seats(1).mount(player1) //passenger mount
@ -123,81 +121,49 @@ class VehicleControlPrepareForDeletionMountedInTest extends FreedContextActorTes
zone.Transport ! Zone.Vehicle.Spawn(lodestar) //can not fake this
"VehicleControl" should {
"if mounted as cargo, self-eject when marked for deconstruction" in {
"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, 500 milliseconds)
val vehicle_msg = vehicleProbe.receiveN(6, 1 minute)
//dismounting as cargo messages
assert(
vehicle_msg.head match {
case VehicleServiceMessage(
_,
VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 0, _))
) =>
true
case _ => false
}
)
assert(
vehicle_msg(1) match {
case VehicleServiceMessage(
_,
VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 68, _))
) =>
true
case _ => false
}
)
assert(
vehicle_msg(2) match {
case VehicleServiceMessage(
"test",
VehicleAction.SendResponse(
_,
CargoMountPointStatusMessage(PlanetSideGUID(2), _, PlanetSideGUID(1), _, 1, CargoStatus.InProgress, 0)
)
) =>
true
case _ => false
}
)
assert(
vehicle_msg(3) match {
case VehicleServiceMessage(
"test",
VehicleAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _, _, _))
) =>
true
case _ => false
}
)
assert(
vehicle_msg(4) match {
case VehicleServiceMessage(
"test",
VehicleAction.SendResponse(
_,
CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0)
)
) =>
true
case _ => false
}
)
//dismounting as cargo messages
//TODO: does not actually kick out the cargo, but instigates the process
assert(
vehicle_msg(5) match {
case VehicleServiceMessage(
"test",
VehicleAction.KickPassenger(PlanetSideGUID(3), 4, false, PlanetSideGUID(1))
) =>
true
case _ => false
}
)
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)
}
}
}
@ -251,55 +217,37 @@ class VehicleControlPrepareForDeletionMountedCargoTest extends FreedContextActor
val vehicle_msg = vehicleProbe.receiveN(6, 500 milliseconds)
vehicle_msg(5) match {
case VehicleServiceMessage(
"test",
VehicleAction.KickPassenger(PlanetSideGUID(4), 4, false, PlanetSideGUID(2))
) => ;
case _ => assert(false)
case VehicleServiceMessage("test", VehicleAction.KickPassenger(PlanetSideGUID(4), 4, true, PlanetSideGUID(2))) => ;
case _ =>
assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-1: ${vehicle_msg(5)}")
}
assert(player2.VehicleSeated.isEmpty)
assert(lodestar.Seats(0).occupant.isEmpty)
//cargo dismounting messages
vehicle_msg.head match {
case VehicleServiceMessage(
_,
VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 0, _))
) => ;
case _ => assert(false)
case VehicleServiceMessage(_, VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 0, _))) => ;
case _ =>
assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-2: ${vehicle_msg.head}")
}
vehicle_msg(1) match {
case VehicleServiceMessage(
_,
VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 68, _))
) => ;
case _ => assert(false)
case VehicleServiceMessage(_, VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 68, _))) => ;
case _ =>
assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-3: ${vehicle_msg(1)}")
}
vehicle_msg(2) match {
case VehicleServiceMessage(
"test",
VehicleAction.SendResponse(
_,
CargoMountPointStatusMessage(PlanetSideGUID(2), _, PlanetSideGUID(1), _, 1, CargoStatus.InProgress, 0)
)
) => ;
case _ => assert(false)
case VehicleServiceMessage("test", VehicleAction.SendResponse(_, CargoMountPointStatusMessage(PlanetSideGUID(2), _, PlanetSideGUID(1), _, 1, CargoStatus.InProgress, 0))) => ;
case _ =>
assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-4: ${vehicle_msg(2)}")
}
vehicle_msg(3) match {
case VehicleServiceMessage(
"test",
VehicleAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _, _, _))
) => ;
case _ => assert(false)
case VehicleServiceMessage("test", VehicleAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _, _, _))) => ;
case _ =>
assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-5: ${vehicle_msg(3)}")
}
vehicle_msg(4) match {
case VehicleServiceMessage(
"test",
VehicleAction.SendResponse(
_,
CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0)
)
) => ;
case _ => assert(false)
case VehicleServiceMessage("test", VehicleAction.SendResponse(_, CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0))) => ;
case _ =>
assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-6: ${vehicle_msg(4)}")
}
}
}