diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala
index 1c2c16c25..73d67cd58 100644
--- a/src/main/scala/net/psforever/actors/session/SessionActor.scala
+++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala
@@ -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()
diff --git a/src/main/scala/net/psforever/actors/zone/ZoneActor.scala b/src/main/scala/net/psforever/actors/zone/ZoneActor.scala
index 149fb0e48..3dec97166 100644
--- a/src/main/scala/net/psforever/actors/zone/ZoneActor.scala
+++ b/src/main/scala/net/psforever/actors/zone/ZoneActor.scala
@@ -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)
diff --git a/src/main/scala/net/psforever/objects/Deployables.scala b/src/main/scala/net/psforever/objects/Deployables.scala
index 8e5b9d333..596a96fb0 100644
--- a/src/main/scala/net/psforever/objects/Deployables.scala
+++ b/src/main/scala/net/psforever/objects/Deployables.scala
@@ -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
}
}
diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
index f98bef474..889582ca3 100644
--- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
+++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
@@ -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"
diff --git a/src/main/scala/net/psforever/objects/Player.scala b/src/main/scala/net/psforever/objects/Player.scala
index 71b7c66e3..05403a288 100644
--- a/src/main/scala/net/psforever/objects/Player.scala
+++ b/src/main/scala/net/psforever/objects/Player.scala
@@ -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)
+ }
+ }
+}
diff --git a/src/main/scala/net/psforever/objects/TurretDeployable.scala b/src/main/scala/net/psforever/objects/TurretDeployable.scala
index c363e625c..013092633 100644
--- a/src/main/scala/net/psforever/objects/TurretDeployable.scala
+++ b/src/main/scala/net/psforever/objects/TurretDeployable.scala
@@ -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)
}
diff --git a/src/main/scala/net/psforever/objects/Vehicle.scala b/src/main/scala/net/psforever/objects/Vehicle.scala
index f21ecebea..b2fdd83a7 100644
--- a/src/main/scala/net/psforever/objects/Vehicle.scala
+++ b/src/main/scala/net/psforever/objects/Vehicle.scala
@@ -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
diff --git a/src/main/scala/net/psforever/objects/Vehicles.scala b/src/main/scala/net/psforever/objects/Vehicles.scala
index eb9c659a9..dc4ee5cdb 100644
--- a/src/main/scala/net/psforever/objects/Vehicles.scala
+++ b/src/main/scala/net/psforever/objects/Vehicles.scala
@@ -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 {
diff --git a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
index 4f653bf72..bf787bdce 100644
--- a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
+++ b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
@@ -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 _ => ;
}
diff --git a/src/main/scala/net/psforever/objects/ballistics/DeployableSource.scala b/src/main/scala/net/psforever/objects/ballistics/DeployableSource.scala
index 26a6fcdc5..6eaa5a619 100644
--- a/src/main/scala/net/psforever/objects/ballistics/DeployableSource.scala
+++ b/src/main/scala/net/psforever/objects/ballistics/DeployableSource.scala
@@ -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
)
diff --git a/src/main/scala/net/psforever/objects/ballistics/VehicleSource.scala b/src/main/scala/net/psforever/objects/ballistics/VehicleSource.scala
index b6f4ad6f8..d347bfb1b 100644
--- a/src/main/scala/net/psforever/objects/ballistics/VehicleSource.scala
+++ b/src/main/scala/net/psforever/objects/ballistics/VehicleSource.scala
@@ -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]
)
}
diff --git a/src/main/scala/net/psforever/objects/definition/DeployableDefinition.scala b/src/main/scala/net/psforever/objects/definition/DeployableDefinition.scala
index 8600bdff0..4f9308292 100644
--- a/src/main/scala/net/psforever/objects/definition/DeployableDefinition.scala
+++ b/src/main/scala/net/psforever/objects/definition/DeployableDefinition.scala
@@ -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
}
diff --git a/src/main/scala/net/psforever/objects/definition/ExoSuitDefinition.scala b/src/main/scala/net/psforever/objects/definition/ExoSuitDefinition.scala
index 1fd9056d8..7a26c32bc 100644
--- a/src/main/scala/net/psforever/objects/definition/ExoSuitDefinition.scala
+++ b/src/main/scala/net/psforever/objects/definition/ExoSuitDefinition.scala
@@ -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
diff --git a/src/main/scala/net/psforever/objects/definition/ObjectDefinition.scala b/src/main/scala/net/psforever/objects/definition/ObjectDefinition.scala
index c42e53ab1..23578022f 100644
--- a/src/main/scala/net/psforever/objects/definition/ObjectDefinition.scala
+++ b/src/main/scala/net/psforever/objects/definition/ObjectDefinition.scala
@@ -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
}
diff --git a/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala b/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala
index feddf851f..f5cbf394e 100644
--- a/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala
+++ b/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala
@@ -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 {
diff --git a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala
index 9efe3dc82..b38056602 100644
--- a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala
@@ -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 }
diff --git a/src/main/scala/net/psforever/objects/serverobject/mount/Mountable.scala b/src/main/scala/net/psforever/objects/serverobject/mount/Mountable.scala
index 16211210e..3d858c4d6 100644
--- a/src/main/scala/net/psforever/objects/serverobject/mount/Mountable.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/mount/Mountable.scala
@@ -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.
diff --git a/src/main/scala/net/psforever/objects/serverobject/mount/MountableBehavior.scala b/src/main/scala/net/psforever/objects/serverobject/mount/MountableBehavior.scala
index 7617efa73..0fb697d23 100644
--- a/src/main/scala/net/psforever/objects/serverobject/mount/MountableBehavior.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/mount/MountableBehavior.scala
@@ -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
}
}
}
diff --git a/src/main/scala/net/psforever/objects/serverobject/mount/MountableEntity.scala b/src/main/scala/net/psforever/objects/serverobject/mount/MountableEntity.scala
new file mode 100644
index 000000000..ed411a695
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/serverobject/mount/MountableEntity.scala
@@ -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
+ }
+}
diff --git a/src/main/scala/net/psforever/objects/serverobject/mount/MountableSpace.scala b/src/main/scala/net/psforever/objects/serverobject/mount/MountableSpace.scala
index 25c046ba7..0db8cd788 100644
--- a/src/main/scala/net/psforever/objects/serverobject/mount/MountableSpace.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/mount/MountableSpace.scala
@@ -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
diff --git a/src/main/scala/net/psforever/objects/vehicles/Cargo.scala b/src/main/scala/net/psforever/objects/vehicles/Cargo.scala
index 736b771e4..e11dce036 100644
--- a/src/main/scala/net/psforever/objects/vehicles/Cargo.scala
+++ b/src/main/scala/net/psforever/objects/vehicles/Cargo.scala
@@ -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
}
diff --git a/src/main/scala/net/psforever/objects/vehicles/CargoBehavior.scala b/src/main/scala/net/psforever/objects/vehicles/CargoBehavior.scala
index 900f5d691..47ea7cdca 100644
--- a/src/main/scala/net/psforever/objects/vehicles/CargoBehavior.scala
+++ b/src/main/scala/net/psforever/objects/vehicles/CargoBehavior.scala
@@ -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)
}
diff --git a/src/main/scala/net/psforever/objects/vehicles/CargoVehicleRestriction.scala b/src/main/scala/net/psforever/objects/vehicles/CargoVehicleRestriction.scala
deleted file mode 100644
index 483fc06ab..000000000
--- a/src/main/scala/net/psforever/objects/vehicles/CargoVehicleRestriction.scala
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (c) 2017 PSForever
-package net.psforever.objects.vehicles
-
-/**
- * An `Enumeration` of exo-suit-based mount access restrictions.
- *
- * 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
-}
diff --git a/src/main/scala/net/psforever/objects/vehicles/CarrierBehavior.scala b/src/main/scala/net/psforever/objects/vehicles/CarrierBehavior.scala
new file mode 100644
index 000000000..d2629f30b
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/vehicles/CarrierBehavior.scala
@@ -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
+ }
+}
+
diff --git a/src/main/scala/net/psforever/objects/vehicles/control/CargoCarrierControl.scala b/src/main/scala/net/psforever/objects/vehicles/control/CargoCarrierControl.scala
index ccfc6c3dd..0c6d30846 100644
--- a/src/main/scala/net/psforever/objects/vehicles/control/CargoCarrierControl.scala
+++ b/src/main/scala/net/psforever/objects/vehicles/control/CargoCarrierControl.scala
@@ -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)
+ }
}
/**
diff --git a/src/main/scala/net/psforever/objects/vehicles/control/DeployingVehicleControl.scala b/src/main/scala/net/psforever/objects/vehicles/control/DeployingVehicleControl.scala
index ef5014eea..2a05d9ec7 100644
--- a/src/main/scala/net/psforever/objects/vehicles/control/DeployingVehicleControl.scala
+++ b/src/main/scala/net/psforever/objects/vehicles/control/DeployingVehicleControl.scala
@@ -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)
}
diff --git a/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala b/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala
index 963d49846..6496d944e 100644
--- a/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala
+++ b/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala
@@ -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 => ;
}
diff --git a/src/main/scala/net/psforever/objects/vital/CollisionData.scala b/src/main/scala/net/psforever/objects/vital/CollisionData.scala
new file mode 100644
index 000000000..50f9f6cbf
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/vital/CollisionData.scala
@@ -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)))
+}
diff --git a/src/main/scala/net/psforever/objects/vital/VitalityDefinition.scala b/src/main/scala/net/psforever/objects/vital/VitalityDefinition.scala
index 1f13f2fab..f3a41f11d 100644
--- a/src/main/scala/net/psforever/objects/vital/VitalityDefinition.scala
+++ b/src/main/scala/net/psforever/objects/vital/VitalityDefinition.scala
@@ -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
}
diff --git a/src/main/scala/net/psforever/objects/vital/base/DamageResolution.scala b/src/main/scala/net/psforever/objects/vital/base/DamageResolution.scala
index b38a92f1c..f47e2b4a3 100644
--- a/src/main/scala/net/psforever/objects/vital/base/DamageResolution.scala
+++ b/src/main/scala/net/psforever/objects/vital/base/DamageResolution.scala
@@ -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
}
diff --git a/src/main/scala/net/psforever/objects/vital/collision/CollisionDamageModifierFunctions.scala b/src/main/scala/net/psforever/objects/vital/collision/CollisionDamageModifierFunctions.scala
new file mode 100644
index 000000000..f3f66e9a3
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/vital/collision/CollisionDamageModifierFunctions.scala
@@ -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
+ }
+ }
+}
diff --git a/src/main/scala/net/psforever/objects/vital/collision/CollisionDamageModifiers.scala b/src/main/scala/net/psforever/objects/vital/collision/CollisionDamageModifiers.scala
new file mode 100644
index 000000000..cf89100e1
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/vital/collision/CollisionDamageModifiers.scala
@@ -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
+ }
+}
diff --git a/src/main/scala/net/psforever/objects/vital/collision/CollisionReason.scala b/src/main/scala/net/psforever/objects/vital/collision/CollisionReason.scala
index e24fd14ec..7b45af571 100644
--- a/src/main/scala/net/psforever/objects/vital/collision/CollisionReason.scala
+++ b/src/main/scala/net/psforever/objects/vital/collision/CollisionReason.scala
@@ -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
+ }
}
diff --git a/src/main/scala/net/psforever/objects/zones/ZoneMap.scala b/src/main/scala/net/psforever/objects/zones/ZoneMap.scala
index 63e34e28e..573fc6f83 100644
--- a/src/main/scala/net/psforever/objects/zones/ZoneMap.scala
+++ b/src/main/scala/net/psforever/objects/zones/ZoneMap.scala
@@ -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.
@@ -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
+ }
}
diff --git a/src/main/scala/net/psforever/objects/zones/blockmap/BlockMap.scala b/src/main/scala/net/psforever/objects/zones/blockmap/BlockMap.scala
index 5aac419de..ca8506b3f 100644
--- a/src/main/scala/net/psforever/objects/zones/blockmap/BlockMap.scala
+++ b/src/main/scala/net/psforever/objects/zones/blockmap/BlockMap.scala
@@ -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)
}
}
diff --git a/src/main/scala/net/psforever/objects/zones/blockmap/BlockMapEntity.scala b/src/main/scala/net/psforever/objects/zones/blockmap/BlockMapEntity.scala
index 325a1fb53..88dd9f97a 100644
--- a/src/main/scala/net/psforever/objects/zones/blockmap/BlockMapEntity.scala
+++ b/src/main/scala/net/psforever/objects/zones/blockmap/BlockMapEntity.scala
@@ -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
+ }
}
}
diff --git a/src/main/scala/net/psforever/packet/game/DamageWithPositionMessage.scala b/src/main/scala/net/psforever/packet/game/DamageWithPositionMessage.scala
index d6e4a2df3..b7cba00c3 100644
--- a/src/main/scala/net/psforever/packet/game/DamageWithPositionMessage.scala
+++ b/src/main/scala/net/psforever/packet/game/DamageWithPositionMessage.scala
@@ -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
}
)
}
diff --git a/src/main/scala/net/psforever/packet/game/DismountVehicleCargoMsg.scala b/src/main/scala/net/psforever/packet/game/DismountVehicleCargoMsg.scala
index 18817aaed..cdea289ff 100644
--- a/src/main/scala/net/psforever/packet/game/DismountVehicleCargoMsg.scala
+++ b/src/main/scala/net/psforever/packet/game/DismountVehicleCargoMsg.scala
@@ -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,
diff --git a/src/main/scala/net/psforever/packet/game/GenericCollisionMsg.scala b/src/main/scala/net/psforever/packet/game/GenericCollisionMsg.scala
index 1e0046b36..57acd3033 100644
--- a/src/main/scala/net/psforever/packet/game/GenericCollisionMsg.scala
+++ b/src/main/scala/net/psforever/packet/game/GenericCollisionMsg.scala
@@ -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.
@@ -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
+ }
+ )
}
diff --git a/src/main/scala/net/psforever/packet/game/MountVehicleCargoMsg.scala b/src/main/scala/net/psforever/packet/game/MountVehicleCargoMsg.scala
index 91649384f..79733d75b 100644
--- a/src/main/scala/net/psforever/packet/game/MountVehicleCargoMsg.scala
+++ b/src/main/scala/net/psforever/packet/game/MountVehicleCargoMsg.scala
@@ -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,
diff --git a/src/main/scala/net/psforever/packet/game/VehicleStateMessage.scala b/src/main/scala/net/psforever/packet/game/VehicleStateMessage.scala
index a00d1ff82..06bec7409 100644
--- a/src/main/scala/net/psforever/packet/game/VehicleStateMessage.scala
+++ b/src/main/scala/net/psforever/packet/game/VehicleStateMessage.scala
@@ -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]
}
diff --git a/src/main/scala/net/psforever/types/Vector3.scala b/src/main/scala/net/psforever/types/Vector3.scala
index ea821ce06..6c604d257 100644
--- a/src/main/scala/net/psforever/types/Vector3.scala
+++ b/src/main/scala/net/psforever/types/Vector3.scala
@@ -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) ::
diff --git a/src/test/scala/CodecTest.scala b/src/test/scala/CodecTest.scala
index fd1a25dda..c49b836e2 100644
--- a/src/test/scala/CodecTest.scala
+++ b/src/test/scala/CodecTest.scala
@@ -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
}
}
}
diff --git a/src/test/scala/game/FireHintMessageTest.scala b/src/test/scala/game/FireHintMessageTest.scala
index 4eeb1c8bd..868b454b8 100644
--- a/src/test/scala/game/FireHintMessageTest.scala
+++ b/src/test/scala/game/FireHintMessageTest.scala
@@ -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
diff --git a/src/test/scala/game/GenericCollisionMsgTest.scala b/src/test/scala/game/GenericCollisionMsgTest.scala
index dca780451..7ef72aa96 100644
--- a/src/test/scala/game/GenericCollisionMsgTest.scala
+++ b/src/test/scala/game/GenericCollisionMsgTest.scala
@@ -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
+ }
}
}
diff --git a/src/test/scala/game/LongRangeProjectileInfoMessageTest.scala b/src/test/scala/game/LongRangeProjectileInfoMessageTest.scala
index 250f6db1f..ca89aaef3 100644
--- a/src/test/scala/game/LongRangeProjectileInfoMessageTest.scala
+++ b/src/test/scala/game/LongRangeProjectileInfoMessageTest.scala
@@ -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
diff --git a/src/test/scala/game/PlayerStateMessageTest.scala b/src/test/scala/game/PlayerStateMessageTest.scala
index 35c5c6021..d33568985 100644
--- a/src/test/scala/game/PlayerStateMessageTest.scala
+++ b/src/test/scala/game/PlayerStateMessageTest.scala
@@ -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,
diff --git a/src/test/scala/game/PlayerStateMessageUpstreamTest.scala b/src/test/scala/game/PlayerStateMessageUpstreamTest.scala
index 8598e385b..e3989d8b0 100644
--- a/src/test/scala/game/PlayerStateMessageUpstreamTest.scala
+++ b/src/test/scala/game/PlayerStateMessageUpstreamTest.scala
@@ -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,
diff --git a/src/test/scala/game/PlayerStateShiftMessageTest.scala b/src/test/scala/game/PlayerStateShiftMessageTest.scala
index dd3043ff1..d37915963 100644
--- a/src/test/scala/game/PlayerStateShiftMessageTest.scala
+++ b/src/test/scala/game/PlayerStateShiftMessageTest.scala
@@ -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
diff --git a/src/test/scala/game/SplashHitMessageTest.scala b/src/test/scala/game/SplashHitMessageTest.scala
index 4c60fc922..5d3ec0392 100644
--- a/src/test/scala/game/SplashHitMessageTest.scala
+++ b/src/test/scala/game/SplashHitMessageTest.scala
@@ -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) ::
diff --git a/src/test/scala/game/VehicleStateMessageTest.scala b/src/test/scala/game/VehicleStateMessageTest.scala
index f6e9045df..3b23700f0 100644
--- a/src/test/scala/game/VehicleStateMessageTest.scala
+++ b/src/test/scala/game/VehicleStateMessageTest.scala
@@ -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,
diff --git a/src/test/scala/game/VehicleSubStateMessageTest.scala b/src/test/scala/game/VehicleSubStateMessageTest.scala
index c84d1ead1..26728c31f 100644
--- a/src/test/scala/game/VehicleSubStateMessageTest.scala
+++ b/src/test/scala/game/VehicleSubStateMessageTest.scala
@@ -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
)
diff --git a/src/test/scala/game/objectcreate/CharacterDataTest.scala b/src/test/scala/game/objectcreate/CharacterDataTest.scala
index 82a7b3760..81e01df66 100644
--- a/src/test/scala/game/objectcreate/CharacterDataTest.scala
+++ b/src/test/scala/game/objectcreate/CharacterDataTest.scala
@@ -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(
diff --git a/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala b/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala
index 9a85310d8..c592df7d6 100644
--- a/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala
+++ b/src/test/scala/game/objectcreatevehicle/MountedVehiclesTest.scala
@@ -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,
diff --git a/src/test/scala/objects/DamageableTest.scala b/src/test/scala/objects/DamageableTest.scala
index 8c2719c48..aa4f6c256 100644
--- a/src/test/scala/objects/DamageableTest.scala
+++ b/src/test/scala/objects/DamageableTest.scala
@@ -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)
diff --git a/src/test/scala/objects/VehicleControlTest.scala b/src/test/scala/objects/VehicleControlTest.scala
index 43645b8fb..e06c4a97f 100644
--- a/src/test/scala/objects/VehicleControlTest.scala
+++ b/src/test/scala/objects/VehicleControlTest.scala
@@ -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)}")
}
}
}