mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-07-15 16:34:42 +00:00
Collisions (#932)
* pattern for applying damage to player avatar and player-controlled vehicle collisions * pattern for applying damage to targets due to collisions, falling damage and crashing damage individually; fields to support these calculations are provided * modifiers to translate 'small step velocity' to real game velocity, as reported by the HUD; corrections for velocity; corrections for velocity in other packets * fall damage calculations moved to function * basic two-body collisions between GUID-identified game entities and a ward against too many collisions in a short amount of time * bailing mechanics * vssm for non-driven vehicles * comment about vehicle state message field * comments and minor refactoring for current collision damage calc; tank_traps modifier; potential fix for blockmap indexing issue * fixed cargo/carrier vehicle ops * corrections to initialization of ce construction items; adjustments to handling of modifiers for collision damage * modifier change, protection against flight speed and spectator crashes; submerged status is once again known only to the actor * appeasing the automated tests * hopefully paced collisions better; re-did how Infantry collisions are calculated, incorporating mass and exo-suit data; kill feed reporting should be better * adjusted damage values again, focusing on the lesser of or middling results; collision killfeed attribution attempt * kicking offers bail protection; lowered the artificial modifier for one kind of collision damage calculation * correction to the local reference map functions * fixed tests; attempt to zero fall damage distance based on velocity; attempt to block mine damage when spectating
This commit is contained in:
parent
0001ef6ce5
commit
93a544c07c
56 changed files with 2024 additions and 1029 deletions
|
|
@ -44,6 +44,7 @@ import net.psforever.objects.vehicles.Utility.InternalTelepad
|
||||||
import net.psforever.objects.vehicles._
|
import net.psforever.objects.vehicles._
|
||||||
import net.psforever.objects.vital._
|
import net.psforever.objects.vital._
|
||||||
import net.psforever.objects.vital.base._
|
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.etc.ExplodingEntityReason
|
||||||
import net.psforever.objects.vital.interaction.DamageInteraction
|
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||||
import net.psforever.objects.vital.projectile.ProjectileReason
|
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 nextSpawnPoint: Option[SpawnPoint] = None
|
||||||
var setupAvatarFunc: () => Unit = AvatarCreate
|
var setupAvatarFunc: () => Unit = AvatarCreate
|
||||||
var setCurrentAvatarFunc: Player => Unit = SetCurrentAvatarNormally
|
var setCurrentAvatarFunc: Player => Unit = SetCurrentAvatarNormally
|
||||||
var persist: () => Unit = NoPersistence
|
var persistFunc: () => Unit = NoPersistence
|
||||||
|
var persist: () => Unit = UpdatePersistenceOnly
|
||||||
var specialItemSlotGuid: Option[PlanetSideGUID] =
|
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.
|
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 setAvatar: Boolean = false
|
||||||
var turnCounterFunc: PlanetSideGUID => Unit = TurnCounterDuringInterim
|
var turnCounterFunc: PlanetSideGUID => Unit = TurnCounterDuringInterim
|
||||||
var waypointCooldown: Long = 0L
|
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 clientKeepAlive: Cancellable = Default.Cancellable
|
||||||
var progressBarUpdate: Cancellable = Default.Cancellable
|
var progressBarUpdate: Cancellable = Default.Cancellable
|
||||||
|
|
@ -1172,6 +1178,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
case NewPlayerLoaded(tplayer) =>
|
case NewPlayerLoaded(tplayer) =>
|
||||||
//new zone
|
//new zone
|
||||||
log.info(s"${tplayer.Name} has spawned into ${session.zone.id}")
|
log.info(s"${tplayer.Name} has spawned into ${session.zone.id}")
|
||||||
|
persist = UpdatePersistenceAndRefs
|
||||||
tplayer.avatar = avatar
|
tplayer.avatar = avatar
|
||||||
session = session.copy(player = tplayer)
|
session = session.copy(player = tplayer)
|
||||||
avatarActor ! AvatarActor.CreateImplants()
|
avatarActor ! AvatarActor.CreateImplants()
|
||||||
|
|
@ -1379,7 +1386,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
case PlayerToken.LoginInfo(name, Zone.Nowhere, _) =>
|
case PlayerToken.LoginInfo(name, Zone.Nowhere, _) =>
|
||||||
log.info(s"LoginInfo: player $name is considered a new character")
|
log.info(s"LoginInfo: player $name is considered a new character")
|
||||||
//TODO poll the database for saved zone and coordinates?
|
//TODO poll the database for saved zone and coordinates?
|
||||||
persist = UpdatePersistence(sender())
|
persistFunc = UpdatePersistence(sender())
|
||||||
deadState = DeadState.RespawnTime
|
deadState = DeadState.RespawnTime
|
||||||
|
|
||||||
session = session.copy(player = new Player(avatar))
|
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) =>
|
case PlayerToken.LoginInfo(playerName, inZone, pos) =>
|
||||||
log.info(s"LoginInfo: player $playerName is already logged in zone ${inZone.id}; rejoining that character")
|
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
|
//tell the old WorldSessionActor to kill itself by using its own subscriptions against itself
|
||||||
inZone.AvatarEvents ! AvatarServiceMessage(playerName, AvatarAction.TeardownConnection())
|
inZone.AvatarEvents ! AvatarServiceMessage(playerName, AvatarAction.TeardownConnection())
|
||||||
//find and reload previous player
|
//find and reload previous player
|
||||||
|
|
@ -1477,18 +1484,36 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update this player avatar for persistence.
|
* Update this player avatar for persistence.
|
||||||
* @param persistRef reference to the persistence monitor
|
* Set to `persist` initially.
|
||||||
*/
|
*/
|
||||||
def UpdatePersistence(persistRef: ActorRef)(): Unit = {
|
def UpdatePersistenceOnly(): Unit = {
|
||||||
persistRef ! AccountPersistenceService.Update(player.Name, continent, player.Position)
|
persistFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update this player avatar for persistence.
|
||||||
|
* Set to `persist` when (new) player is loaded.
|
||||||
|
*/
|
||||||
|
def UpdatePersistenceAndRefs(): Unit = {
|
||||||
|
persistFunc()
|
||||||
updateOldRefsMap()
|
updateOldRefsMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Do not update this player avatar for persistence.
|
* Do not update this player avatar for persistence.
|
||||||
|
* Set to `persistFunc` initially.
|
||||||
*/
|
*/
|
||||||
def NoPersistence(): Unit = {}
|
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.
|
* A zoning message was received.
|
||||||
* That doesn't matter.
|
* That doesn't matter.
|
||||||
|
|
@ -1585,11 +1610,11 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
CancelAllProximityUnits()
|
CancelAllProximityUnits()
|
||||||
//droppod action
|
//droppod action
|
||||||
val droppod = Vehicle(GlobalDefinitions.droppod)
|
val droppod = Vehicle(GlobalDefinitions.droppod)
|
||||||
|
droppod.GUID = PlanetSideGUID(0) //droppod is not registered, we must jury-rig this
|
||||||
droppod.Faction = player.Faction
|
droppod.Faction = player.Faction
|
||||||
droppod.Position = spawnPosition.xy + Vector3.z(1024)
|
droppod.Position = spawnPosition.xy + Vector3.z(1024)
|
||||||
droppod.Orientation = Vector3.z(180) //you always seems to land looking south; don't know why
|
droppod.Orientation = Vector3.z(180) //you always seems to land looking south; don't know why
|
||||||
droppod.Seats(0).mount(player)
|
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
|
droppod.Invalidate() //now, we must short-circuit the jury-rig
|
||||||
interstellarFerry = Some(droppod) //leverage vehicle gating
|
interstellarFerry = Some(droppod) //leverage vehicle gating
|
||||||
player.Position = droppod.Position
|
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 = {
|
def HandleAvatarServiceResponse(toChannel: String, guid: PlanetSideGUID, reply: AvatarResponse.Response): Unit = {
|
||||||
val tplayer_guid =
|
val tplayer_guid =
|
||||||
if (player.HasGUID) player.GUID
|
if (player != null && player.HasGUID) player.GUID
|
||||||
else PlanetSideGUID(0)
|
else PlanetSideGUID(0)
|
||||||
reply match {
|
reply match {
|
||||||
case AvatarResponse.TeardownConnection() =>
|
case AvatarResponse.TeardownConnection() =>
|
||||||
|
|
@ -2525,12 +2550,12 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
obj.Actor ! Vehicle.Deconstruct()
|
obj.Actor ! Vehicle.Deconstruct()
|
||||||
|
|
||||||
case Mountable.CanDismount(obj: Vehicle, seat_num, _) =>
|
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
|
val player_guid: PlanetSideGUID = tplayer.GUID
|
||||||
if (player_guid == player.GUID) {
|
if (player_guid == player.GUID) {
|
||||||
//disembarking self
|
//disembarking self
|
||||||
|
log.info(
|
||||||
|
s"${tplayer.Name} dismounts a ${obj.Definition.asInstanceOf[ObjectDefinition].Name} from seat #$seat_num"
|
||||||
|
)
|
||||||
ConditionalDriverVehicleControl(obj)
|
ConditionalDriverVehicleControl(obj)
|
||||||
UnaccessContainer(obj)
|
UnaccessContainer(obj)
|
||||||
DismountAction(tplayer, obj, seat_num)
|
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
|
//but always seems to return 4 if user is kicked by mount permissions changing
|
||||||
sendResponse(DismountVehicleMsg(guid, BailType.Kicked, wasKickedByDriver))
|
sendResponse(DismountVehicleMsg(guid, BailType.Kicked, wasKickedByDriver))
|
||||||
if (tplayer_guid == guid) {
|
if (tplayer_guid == guid) {
|
||||||
log.info(s"{${player.Name} has been kicked from ${player.Sex.possessive} ride!")
|
val typeOfRide = continent.GUID(vehicle_guid) match {
|
||||||
continent.GUID(vehicle_guid) match {
|
|
||||||
case Some(obj: Vehicle) =>
|
case Some(obj: Vehicle) =>
|
||||||
UnaccessContainer(obj)
|
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) =>
|
case VehicleResponse.InventoryState2(obj_guid, parent_guid, value) =>
|
||||||
|
|
@ -2969,7 +2996,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
cargo: Vehicle,
|
cargo: Vehicle,
|
||||||
mountPoint: Int
|
mountPoint: Int
|
||||||
): (ObjectAttachMessage, CargoMountPointStatusMessage) = {
|
): (ObjectAttachMessage, CargoMountPointStatusMessage) = {
|
||||||
val msgs @ (attachMessage, mountPointStatusMessage) = CargoBehavior.CargoMountMessages(carrier, cargo, mountPoint)
|
val msgs @ (attachMessage, mountPointStatusMessage) = CarrierBehavior.CargoMountMessages(carrier, cargo, mountPoint)
|
||||||
CargoMountMessagesForUs(attachMessage, mountPointStatusMessage)
|
CargoMountMessagesForUs(attachMessage, mountPointStatusMessage)
|
||||||
msgs
|
msgs
|
||||||
}
|
}
|
||||||
|
|
@ -3336,8 +3363,8 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
(continent.GUID(cargo_guid), continent.GUID(carrier_guid)) match {
|
(continent.GUID(cargo_guid), continent.GUID(carrier_guid)) match {
|
||||||
case (Some(cargo: Vehicle), Some(carrier: Vehicle)) =>
|
case (Some(cargo: Vehicle), Some(carrier: Vehicle)) =>
|
||||||
carrier.CargoHolds.find({ case (_, hold) => !hold.isOccupied }) match {
|
carrier.CargoHolds.find({ case (_, hold) => !hold.isOccupied }) match {
|
||||||
case Some((mountPoint, _)) => //try begin the mount process
|
case Some((mountPoint, _)) =>
|
||||||
cargo.Actor ! CargoBehavior.CheckCargoMounting(carrier_guid, mountPoint, 0)
|
cargo.Actor ! CargoBehavior.StartCargoMounting(carrier_guid, mountPoint)
|
||||||
case _ =>
|
case _ =>
|
||||||
log.warn(
|
log.warn(
|
||||||
s"MountVehicleCargoMsg: ${player.Name} trying to load cargo into a ${carrier.Definition.Name} which oes not have a cargo hold"
|
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 _ => ;
|
||||||
}
|
}
|
||||||
|
|
||||||
case msg @ DismountVehicleCargoMsg(player_guid, cargo_guid, bailed, requestedByPassenger, kicked) =>
|
case msg @ DismountVehicleCargoMsg(_, cargo_guid, bailed, _, kicked) =>
|
||||||
log.debug(s"DismountVehicleCargoMsg: $msg")
|
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 {
|
continent.GUID(cargo_guid) match {
|
||||||
case Some(cargo: Vehicle) if !requestedByPassenger =>
|
case Some(cargo: Vehicle) =>
|
||||||
continent.GUID(cargo.MountedIn) match {
|
cargo.Actor ! CargoBehavior.StartCargoDismounting(bailed || kicked)
|
||||||
case Some(carrier: Vehicle) =>
|
|
||||||
CargoBehavior.HandleVehicleCargoDismount(continent, cargo_guid, bailed, requestedByPassenger, kicked)
|
|
||||||
case _ => ;
|
|
||||||
}
|
|
||||||
case _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3633,7 +3654,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
case vehicle if vehicle.CargoHolds.nonEmpty =>
|
case vehicle if vehicle.CargoHolds.nonEmpty =>
|
||||||
vehicle.CargoHolds.collect {
|
vehicle.CargoHolds.collect {
|
||||||
case (_index, hold: Cargo) if hold.isOccupied =>
|
case (_index, hold: Cargo) if hold.isOccupied =>
|
||||||
CargoBehavior.CargoMountBehaviorForAll(
|
CarrierBehavior.CargoMountBehaviorForAll(
|
||||||
vehicle,
|
vehicle,
|
||||||
hold.occupant.get,
|
hold.occupant.get,
|
||||||
_index
|
_index
|
||||||
|
|
@ -3779,6 +3800,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
if (isMovingPlus) {
|
if (isMovingPlus) {
|
||||||
CancelZoningProcessWithDescriptiveReason("cancel_motion")
|
CancelZoningProcessWithDescriptiveReason("cancel_motion")
|
||||||
}
|
}
|
||||||
|
fallHeightTracker(pos.z)
|
||||||
// if (is_crouching && !player.Crouching) {
|
// if (is_crouching && !player.Crouching) {
|
||||||
// //dev stuff goes here
|
// //dev stuff goes here
|
||||||
// }
|
// }
|
||||||
|
|
@ -3909,10 +3931,10 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
//we're driving the vehicle
|
//we're driving the vehicle
|
||||||
persist()
|
persist()
|
||||||
turnCounterFunc(player.GUID)
|
turnCounterFunc(player.GUID)
|
||||||
|
fallHeightTracker(pos.z)
|
||||||
if (obj.MountedIn.isEmpty) {
|
if (obj.MountedIn.isEmpty) {
|
||||||
updateBlockMap(obj, continent, pos)
|
updateBlockMap(obj, continent, pos)
|
||||||
}
|
}
|
||||||
val seat = obj.Seats(0)
|
|
||||||
player.Position = pos //convenient
|
player.Position = pos //convenient
|
||||||
if (obj.WeaponControlledFromSeat(0).isEmpty) {
|
if (obj.WeaponControlledFromSeat(0).isEmpty) {
|
||||||
player.Orientation = Vector3.z(ang.z) //convenient
|
player.Orientation = Vector3.z(ang.z) //convenient
|
||||||
|
|
@ -3969,10 +3991,38 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
KickedByAdministration()
|
KickedByAdministration()
|
||||||
}
|
}
|
||||||
|
|
||||||
case msg @ VehicleSubStateMessage(vehicle_guid, player_guid, vehicle_pos, vehicle_ang, vel, unk1, unk2) =>
|
case msg @ VehicleSubStateMessage(vehicle_guid, _, pos, ang, vel, unk1, unk2) =>
|
||||||
log.debug(
|
//log.info(s"msg")
|
||||||
s"VehicleSubState: $vehicle_guid, ${player.Name}_guid, $vehicle_pos, $vehicle_ang, $vel, $unk1, $unk2"
|
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) =>
|
case msg @ ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, seq, end, target_guid) =>
|
||||||
val index = projectile_guid.guid - Projectile.baseUID
|
val index = projectile_guid.guid - Projectile.baseUID
|
||||||
|
|
@ -5447,7 +5497,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
case Some(obj: Mountable) =>
|
case Some(obj: Mountable) =>
|
||||||
obj.PassengerInSeat(player) match {
|
obj.PassengerInSeat(player) match {
|
||||||
case Some(seat_num) =>
|
case Some(seat_num) =>
|
||||||
obj.Actor ! Mountable.TryDismount(player, seat_num)
|
obj.Actor ! Mountable.TryDismount(player, seat_num, bailType)
|
||||||
if (interstellarFerry.isDefined) {
|
if (interstellarFerry.isDefined) {
|
||||||
//short-circuit the temporary channel for transferring between zones, the player is no longer doing that
|
//short-circuit the temporary channel for transferring between zones, the player is no longer doing that
|
||||||
//see above in VehicleResponse.TransferPassenger case
|
//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)) =>
|
case (Some(obj: Mountable), Some(tplayer: Player)) =>
|
||||||
obj.PassengerInSeat(tplayer) match {
|
obj.PassengerInSeat(tplayer) match {
|
||||||
case Some(seat_num) =>
|
case Some(seat_num) =>
|
||||||
obj.Actor ! Mountable.TryDismount(tplayer, seat_num)
|
obj.Actor ! Mountable.TryDismount(tplayer, seat_num, bailType)
|
||||||
case None =>
|
case None =>
|
||||||
dismountWarning(
|
dismountWarning(
|
||||||
s"DismountVehicleMsg: can not find where other player ${player.Name}_guid is seated in mountable $obj_guid"
|
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))
|
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) =>
|
case msg @ GenericCollisionMsg(ctype, p, php, ppos, pv, t, thp, tpos, tv, u1, u2, u3) =>
|
||||||
log.info(s"${player.Name} would be in intense and excruciating pain right now if collision worked")
|
//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(
|
case msg @ BugReportMessage(
|
||||||
version_major,
|
version_major,
|
||||||
|
|
@ -6760,6 +6913,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
progressBarValue = None
|
progressBarValue = None
|
||||||
lastTerminalOrderFulfillment = true
|
lastTerminalOrderFulfillment = true
|
||||||
kitToBeUsed = None
|
kitToBeUsed = None
|
||||||
|
collisionHistory.clear()
|
||||||
accessedContainer match {
|
accessedContainer match {
|
||||||
case Some(v: Vehicle) =>
|
case Some(v: Vehicle) =>
|
||||||
val vguid = v.GUID
|
val vguid = v.GUID
|
||||||
|
|
@ -6863,7 +7017,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
)
|
)
|
||||||
carrierInfo match {
|
carrierInfo match {
|
||||||
case (Some(carrier), Some((index, _))) =>
|
case (Some(carrier), Some((index, _))) =>
|
||||||
CargoBehavior.CargoMountBehaviorForOthers(carrier, vehicle, index, player.GUID)
|
CarrierBehavior.CargoMountBehaviorForOthers(carrier, vehicle, index, player.GUID)
|
||||||
case _ =>
|
case _ =>
|
||||||
vehicle.MountedIn = None
|
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 = {
|
def DismountAction(tplayer: Player, obj: PlanetSideGameObject with Mountable, seatNum: Int): Unit = {
|
||||||
val player_guid: PlanetSideGUID = tplayer.GUID
|
val player_guid: PlanetSideGUID = tplayer.GUID
|
||||||
keepAliveFunc = NormalKeepAlive
|
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.VehicleEvents ! VehicleServiceMessage(
|
||||||
continent.id,
|
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()
|
val func = data.calculate()
|
||||||
target match {
|
target match {
|
||||||
case obj: Player if obj.CanDamage && obj.Actor != Default.Actor =>
|
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
|
// auto kick players damaging spectators
|
||||||
if (obj.spectator && obj != player) {
|
if (obj.spectator && obj != player) {
|
||||||
AdministrativeKick(player)
|
AdministrativeKick(player)
|
||||||
} else {
|
} else {
|
||||||
obj.Actor ! Vitality.Damage(func)
|
obj.Actor ! Vitality.Damage(func)
|
||||||
}
|
}
|
||||||
|
|
||||||
case obj: Vehicle if obj.CanDamage =>
|
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)
|
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 _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8197,7 +8379,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
log.warn(
|
log.warn(
|
||||||
s"LoadZoneInVehicleAsDriver: ${player.Name} must eject cargo in hold $index; vehicle is missing driver"
|
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 =>
|
case entry =>
|
||||||
val cargo = vehicle.CargoHolds(entry.mount).occupant.get
|
val cargo = vehicle.CargoHolds(entry.mount).occupant.get
|
||||||
continent.VehicleEvents ! VehicleServiceMessage(
|
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]()
|
var oldRefsMap: mutable.HashMap[PlanetSideGUID, String] = new mutable.HashMap[PlanetSideGUID, String]()
|
||||||
def updateOldRefsMap(): Unit = {
|
def updateOldRefsMap(): Unit = {
|
||||||
oldRefsMap.addAll(
|
if(player.HasGUID) {
|
||||||
(continent.GUID(player.VehicleSeated) match {
|
oldRefsMap.addAll(
|
||||||
case Some(v: Vehicle) =>
|
(continent.GUID(player.VehicleSeated) match {
|
||||||
v.Weapons.toList.collect {
|
case Some(v : Vehicle) =>
|
||||||
case (_, slot: EquipmentSlot) if slot.Equipment.nonEmpty => updateOldRefsMap(slot.Equipment.get)
|
v.Weapons.toList.collect {
|
||||||
}.flatten ++
|
case (_, slot : EquipmentSlot) if slot.Equipment.nonEmpty => updateOldRefsMap(slot.Equipment.get)
|
||||||
updateOldRefsMap(v.Inventory)
|
}.flatten ++
|
||||||
case _ =>
|
updateOldRefsMap(v.Inventory)
|
||||||
Map.empty[PlanetSideGUID, String]
|
case _ =>
|
||||||
}) ++
|
Map.empty[PlanetSideGUID, String]
|
||||||
(accessedContainer match {
|
}) ++
|
||||||
case Some(cont) => updateOldRefsMap(cont.Inventory)
|
(accessedContainer match {
|
||||||
case None => Map.empty[PlanetSideGUID, String]
|
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)
|
player.Holsters().toList.collect {
|
||||||
}.flatten ++
|
case slot if slot.Equipment.nonEmpty => updateOldRefsMap(slot.Equipment.get)
|
||||||
updateOldRefsMap(player.Inventory) ++
|
}.flatten ++
|
||||||
updateOldRefsMap(player.avatar.locker.Inventory)
|
updateOldRefsMap(player.Inventory) ++
|
||||||
)
|
updateOldRefsMap(player.avatar.locker.Inventory)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def updateOldRefsMap(inventory: net.psforever.objects.inventory.GridInventory): IterableOnce[(PlanetSideGUID, String)] = {
|
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) = {
|
def failWithError(error: String) = {
|
||||||
log.error(error)
|
log.error(error)
|
||||||
middlewareActor ! MiddlewareActor.Teardown()
|
middlewareActor ! MiddlewareActor.Teardown()
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,7 @@ class ZoneActor(context: ActorContext[ZoneActor.Command], zone: Zone)
|
||||||
zone.blockMap.addTo(target, toPosition)
|
zone.blockMap.addTo(target, toPosition)
|
||||||
|
|
||||||
case UpdateBlockMap(target, toPosition) =>
|
case UpdateBlockMap(target, toPosition) =>
|
||||||
zone.blockMap.move(target, toPosition)
|
target.updateBlockMapEntry(toPosition)
|
||||||
|
|
||||||
case RemoveFromBlockMap(target) =>
|
case RemoveFromBlockMap(target) =>
|
||||||
zone.blockMap.removeFrom(target)
|
zone.blockMap.removeFrom(target)
|
||||||
|
|
|
||||||
|
|
@ -146,19 +146,30 @@ object Deployables {
|
||||||
* If the default ammunition mode for the `ConstructionTool` is not supported by the given certifications,
|
* 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.
|
* find a suitable ammunition mode and switch to it internally.
|
||||||
* No special complaint is raised if the `ConstructionItem` itself is completely unsupported.
|
* 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 certs the certification baseline being compared against
|
||||||
* @param obj the `ConstructionItem` entity
|
* @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
|
* `false`, otherwise
|
||||||
*/
|
*/
|
||||||
def initializeConstructionAmmoMode(
|
def initializeConstructionItem(
|
||||||
certs: Set[Certification],
|
certs: Set[Certification],
|
||||||
obj: ConstructionItem
|
obj: ConstructionItem
|
||||||
): Boolean = {
|
): Boolean = {
|
||||||
|
val initialFireModeIndex = obj.FireModeIndex
|
||||||
if (!Deployables.constructionItemPermissionComparison(certs, obj.ModePermissions)) {
|
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 {
|
} else {
|
||||||
false
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,22 +31,28 @@ import net.psforever.objects.vital.damage._
|
||||||
import net.psforever.objects.vital.etc.ExplodingRadialDegrade
|
import net.psforever.objects.vital.etc.ExplodingRadialDegrade
|
||||||
import net.psforever.objects.vital.projectile._
|
import net.psforever.objects.vital.projectile._
|
||||||
import net.psforever.objects.vital.prop.DamageWithPosition
|
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.{ExoSuitType, ImplantType, PlanetSideEmpire, Vector3}
|
||||||
import net.psforever.types._
|
import net.psforever.types._
|
||||||
import net.psforever.objects.serverobject.llu.{CaptureFlagDefinition, CaptureFlagSocketDefinition}
|
import net.psforever.objects.serverobject.llu.{CaptureFlagDefinition, CaptureFlagSocketDefinition}
|
||||||
|
import net.psforever.objects.vital.collision.TrapCollisionDamageMultiplier
|
||||||
|
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
object GlobalDefinitions {
|
object GlobalDefinitions {
|
||||||
// Characters
|
/*
|
||||||
|
characters
|
||||||
|
*/
|
||||||
val avatar = new AvatarDefinition(121)
|
val avatar = new AvatarDefinition(121)
|
||||||
avatar.MaxHealth = 100
|
avatar.MaxHealth = 100
|
||||||
avatar.Damageable = true
|
avatar.Damageable = true
|
||||||
avatar.DrownAtMaxDepth = true
|
avatar.DrownAtMaxDepth = true
|
||||||
avatar.MaxDepth = 1.609375f //Male, standing, not MAX
|
avatar.MaxDepth = 1.609375f //Male, standing, not MAX
|
||||||
avatar.UnderwaterLifespan(suffocation = 60000L, recovery = 10000L)
|
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
|
exo-suits
|
||||||
*/
|
*/
|
||||||
|
|
@ -1704,6 +1710,8 @@ object GlobalDefinitions {
|
||||||
Standard.ResistanceDirectHit = 4
|
Standard.ResistanceDirectHit = 4
|
||||||
Standard.ResistanceSplash = 15
|
Standard.ResistanceSplash = 15
|
||||||
Standard.ResistanceAggravated = 8
|
Standard.ResistanceAggravated = 8
|
||||||
|
Standard.collision.forceFactor = 1.5f
|
||||||
|
Standard.collision.massFactor = 2f
|
||||||
|
|
||||||
Agile.Name = "lite_armor"
|
Agile.Name = "lite_armor"
|
||||||
Agile.Descriptor = "agile"
|
Agile.Descriptor = "agile"
|
||||||
|
|
@ -1717,6 +1725,8 @@ object GlobalDefinitions {
|
||||||
Agile.ResistanceDirectHit = 6
|
Agile.ResistanceDirectHit = 6
|
||||||
Agile.ResistanceSplash = 25
|
Agile.ResistanceSplash = 25
|
||||||
Agile.ResistanceAggravated = 10
|
Agile.ResistanceAggravated = 10
|
||||||
|
Agile.collision.forceFactor = 1.5f
|
||||||
|
Agile.collision.massFactor = 2f
|
||||||
|
|
||||||
Reinforced.Name = "med_armor"
|
Reinforced.Name = "med_armor"
|
||||||
Reinforced.Descriptor = "reinforced"
|
Reinforced.Descriptor = "reinforced"
|
||||||
|
|
@ -1732,6 +1742,8 @@ object GlobalDefinitions {
|
||||||
Reinforced.ResistanceDirectHit = 10
|
Reinforced.ResistanceDirectHit = 10
|
||||||
Reinforced.ResistanceSplash = 35
|
Reinforced.ResistanceSplash = 35
|
||||||
Reinforced.ResistanceAggravated = 12
|
Reinforced.ResistanceAggravated = 12
|
||||||
|
Reinforced.collision.forceFactor = 2f
|
||||||
|
Reinforced.collision.massFactor = 3f
|
||||||
|
|
||||||
Infiltration.Name = "infiltration_suit"
|
Infiltration.Name = "infiltration_suit"
|
||||||
Infiltration.Permissions = List(Certification.InfiltrationSuit)
|
Infiltration.Permissions = List(Certification.InfiltrationSuit)
|
||||||
|
|
@ -1752,6 +1764,8 @@ object GlobalDefinitions {
|
||||||
max.ResistanceDirectHit = 6
|
max.ResistanceDirectHit = 6
|
||||||
max.ResistanceSplash = 35
|
max.ResistanceSplash = 35
|
||||||
max.ResistanceAggravated = 10
|
max.ResistanceAggravated = 10
|
||||||
|
max.collision.forceFactor = 4f
|
||||||
|
max.collision.massFactor = 10f
|
||||||
max.DamageUsing = DamageCalculations.AgainstMaxSuit
|
max.DamageUsing = DamageCalculations.AgainstMaxSuit
|
||||||
max.Model = MaxResolutions.calculate
|
max.Model = MaxResolutions.calculate
|
||||||
}
|
}
|
||||||
|
|
@ -5652,10 +5666,17 @@ object GlobalDefinitions {
|
||||||
* Initialize `VehicleDefinition` globals.
|
* Initialize `VehicleDefinition` globals.
|
||||||
*/
|
*/
|
||||||
private def init_vehicles(): Unit = {
|
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 atvForm = GeometryForm.representByCylinder(radius = 1.1797f, height = 1.1875f) _
|
||||||
val delivererForm = GeometryForm.representByCylinder(radius = 2.46095f, height = 2.40626f) _ //TODO hexahedron
|
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 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() {
|
val bailableSeat = new SeatDefinition() {
|
||||||
bailable = true
|
bailable = true
|
||||||
|
|
@ -5694,6 +5715,11 @@ object GlobalDefinitions {
|
||||||
fury.MaxDepth = 1.3f
|
fury.MaxDepth = 1.3f
|
||||||
fury.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
fury.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||||
fury.Geometry = atvForm
|
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.Name = "quadassault" // Basilisk
|
||||||
quadassault.MaxHealth = 650
|
quadassault.MaxHealth = 650
|
||||||
|
|
@ -5725,6 +5751,11 @@ object GlobalDefinitions {
|
||||||
quadassault.MaxDepth = 1.3f
|
quadassault.MaxDepth = 1.3f
|
||||||
quadassault.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
quadassault.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||||
quadassault.Geometry = atvForm
|
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.Name = "quadstealth" // Wraith
|
||||||
quadstealth.MaxHealth = 650
|
quadstealth.MaxHealth = 650
|
||||||
|
|
@ -5756,6 +5787,11 @@ object GlobalDefinitions {
|
||||||
quadstealth.MaxDepth = 1.25f
|
quadstealth.MaxDepth = 1.25f
|
||||||
quadstealth.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
quadstealth.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||||
quadstealth.Geometry = atvForm
|
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.Name = "two_man_assault_buggy" // Harasser
|
||||||
two_man_assault_buggy.MaxHealth = 1250
|
two_man_assault_buggy.MaxHealth = 1250
|
||||||
|
|
@ -5788,6 +5824,11 @@ object GlobalDefinitions {
|
||||||
two_man_assault_buggy.MaxDepth = 1.5f
|
two_man_assault_buggy.MaxDepth = 1.5f
|
||||||
two_man_assault_buggy.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
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.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.Name = "skyguard"
|
||||||
skyguard.MaxHealth = 1000
|
skyguard.MaxHealth = 1000
|
||||||
|
|
@ -5821,6 +5862,11 @@ object GlobalDefinitions {
|
||||||
skyguard.MaxDepth = 1.5f
|
skyguard.MaxDepth = 1.5f
|
||||||
skyguard.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
skyguard.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||||
skyguard.Geometry = GeometryForm.representByCylinder(radius = 1.8867f, height = 1.4375f)
|
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.Name = "threemanheavybuggy" // Marauder
|
||||||
threemanheavybuggy.MaxHealth = 1700
|
threemanheavybuggy.MaxHealth = 1700
|
||||||
|
|
@ -5858,6 +5904,11 @@ object GlobalDefinitions {
|
||||||
threemanheavybuggy.MaxDepth = 1.83f
|
threemanheavybuggy.MaxDepth = 1.83f
|
||||||
threemanheavybuggy.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
threemanheavybuggy.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||||
threemanheavybuggy.Geometry = GeometryForm.representByCylinder(radius = 2.1953f, height = 2.03125f)
|
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.Name = "twomanheavybuggy" // Enforcer
|
||||||
twomanheavybuggy.MaxHealth = 1800
|
twomanheavybuggy.MaxHealth = 1800
|
||||||
|
|
@ -5891,6 +5942,11 @@ object GlobalDefinitions {
|
||||||
twomanheavybuggy.MaxDepth = 1.95f
|
twomanheavybuggy.MaxDepth = 1.95f
|
||||||
twomanheavybuggy.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
twomanheavybuggy.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||||
twomanheavybuggy.Geometry = GeometryForm.representByCylinder(radius = 2.60935f, height = 1.79688f)
|
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.Name = "twomanhoverbuggy" // Thresher
|
||||||
twomanhoverbuggy.MaxHealth = 1600
|
twomanhoverbuggy.MaxHealth = 1600
|
||||||
|
|
@ -5926,6 +5982,11 @@ object GlobalDefinitions {
|
||||||
recovery = 5000L
|
recovery = 5000L
|
||||||
) //but the thresher hovers over water, so ...?
|
) //but the thresher hovers over water, so ...?
|
||||||
twomanhoverbuggy.Geometry = GeometryForm.representByCylinder(radius = 2.1875f, height = 2.01563f)
|
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.Name = "mediumtransport" // Deliverer
|
||||||
mediumtransport.MaxHealth = 2500
|
mediumtransport.MaxHealth = 2500
|
||||||
|
|
@ -5969,6 +6030,11 @@ object GlobalDefinitions {
|
||||||
mediumtransport.MaxDepth = 1.2f
|
mediumtransport.MaxDepth = 1.2f
|
||||||
mediumtransport.UnderwaterLifespan(suffocation = -1, recovery = -1)
|
mediumtransport.UnderwaterLifespan(suffocation = -1, recovery = -1)
|
||||||
mediumtransport.Geometry = delivererForm
|
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.Name = "battlewagon" // Raider
|
||||||
battlewagon.MaxHealth = 2500
|
battlewagon.MaxHealth = 2500
|
||||||
|
|
@ -6015,6 +6081,11 @@ object GlobalDefinitions {
|
||||||
battlewagon.MaxDepth = 1.2f
|
battlewagon.MaxDepth = 1.2f
|
||||||
battlewagon.UnderwaterLifespan(suffocation = -1, recovery = -1)
|
battlewagon.UnderwaterLifespan(suffocation = -1, recovery = -1)
|
||||||
battlewagon.Geometry = delivererForm
|
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.Name = "thunderer"
|
||||||
thunderer.MaxHealth = 2500
|
thunderer.MaxHealth = 2500
|
||||||
|
|
@ -6058,6 +6129,11 @@ object GlobalDefinitions {
|
||||||
thunderer.MaxDepth = 1.2f
|
thunderer.MaxDepth = 1.2f
|
||||||
thunderer.UnderwaterLifespan(suffocation = -1, recovery = -1)
|
thunderer.UnderwaterLifespan(suffocation = -1, recovery = -1)
|
||||||
thunderer.Geometry = delivererForm
|
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.Name = "aurora"
|
||||||
aurora.MaxHealth = 2500
|
aurora.MaxHealth = 2500
|
||||||
|
|
@ -6101,6 +6177,11 @@ object GlobalDefinitions {
|
||||||
aurora.MaxDepth = 1.2f
|
aurora.MaxDepth = 1.2f
|
||||||
aurora.UnderwaterLifespan(suffocation = -1, recovery = -1)
|
aurora.UnderwaterLifespan(suffocation = -1, recovery = -1)
|
||||||
aurora.Geometry = delivererForm
|
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.Name = "apc_tr" // Juggernaut
|
||||||
apc_tr.MaxHealth = 6000
|
apc_tr.MaxHealth = 6000
|
||||||
|
|
@ -6164,6 +6245,11 @@ object GlobalDefinitions {
|
||||||
apc_tr.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
|
apc_tr.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
|
||||||
apc_tr.Geometry = apcForm
|
apc_tr.Geometry = apcForm
|
||||||
apc_tr.MaxCapacitor = 300
|
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.Name = "apc_nc" // Vindicator
|
||||||
apc_nc.MaxHealth = 6000
|
apc_nc.MaxHealth = 6000
|
||||||
|
|
@ -6227,6 +6313,11 @@ object GlobalDefinitions {
|
||||||
apc_nc.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
|
apc_nc.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
|
||||||
apc_nc.Geometry = apcForm
|
apc_nc.Geometry = apcForm
|
||||||
apc_nc.MaxCapacitor = 300
|
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.Name = "apc_vs" // Leviathan
|
||||||
apc_vs.MaxHealth = 6000
|
apc_vs.MaxHealth = 6000
|
||||||
|
|
@ -6290,6 +6381,11 @@ object GlobalDefinitions {
|
||||||
apc_vs.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
|
apc_vs.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
|
||||||
apc_vs.Geometry = apcForm
|
apc_vs.Geometry = apcForm
|
||||||
apc_vs.MaxCapacitor = 300
|
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.Name = "lightning"
|
||||||
lightning.MaxHealth = 2000
|
lightning.MaxHealth = 2000
|
||||||
|
|
@ -6324,6 +6420,11 @@ object GlobalDefinitions {
|
||||||
lightning.MaxDepth = 1.38f
|
lightning.MaxDepth = 1.38f
|
||||||
lightning.UnderwaterLifespan(suffocation = 12000L, recovery = 6000L)
|
lightning.UnderwaterLifespan(suffocation = 12000L, recovery = 6000L)
|
||||||
lightning.Geometry = GeometryForm.representByCylinder(radius = 2.5078f, height = 1.79688f)
|
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.Name = "prowler"
|
||||||
prowler.MaxHealth = 4800
|
prowler.MaxHealth = 4800
|
||||||
|
|
@ -6363,6 +6464,11 @@ object GlobalDefinitions {
|
||||||
prowler.MaxDepth = 3
|
prowler.MaxDepth = 3
|
||||||
prowler.UnderwaterLifespan(suffocation = 12000L, recovery = 6000L)
|
prowler.UnderwaterLifespan(suffocation = 12000L, recovery = 6000L)
|
||||||
prowler.Geometry = GeometryForm.representByCylinder(radius = 3.461f, height = 3.48438f)
|
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.Name = "vanguard"
|
||||||
vanguard.MaxHealth = 5400
|
vanguard.MaxHealth = 5400
|
||||||
|
|
@ -6398,6 +6504,11 @@ object GlobalDefinitions {
|
||||||
vanguard.MaxDepth = 2.7f
|
vanguard.MaxDepth = 2.7f
|
||||||
vanguard.UnderwaterLifespan(suffocation = 12000L, recovery = 6000L)
|
vanguard.UnderwaterLifespan(suffocation = 12000L, recovery = 6000L)
|
||||||
vanguard.Geometry = GeometryForm.representByCylinder(radius = 3.8554f, height = 2.60938f)
|
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.Name = "magrider"
|
||||||
magrider.MaxHealth = 4200
|
magrider.MaxHealth = 4200
|
||||||
|
|
@ -6435,6 +6546,11 @@ object GlobalDefinitions {
|
||||||
magrider.MaxDepth = 2
|
magrider.MaxDepth = 2
|
||||||
magrider.UnderwaterLifespan(suffocation = 45000L, recovery = 5000L) //but the magrider hovers over water, so ...?
|
magrider.UnderwaterLifespan(suffocation = 45000L, recovery = 5000L) //but the magrider hovers over water, so ...?
|
||||||
magrider.Geometry = GeometryForm.representByCylinder(radius = 3.3008f, height = 3.26562f)
|
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
|
val utilityConverter = new UtilityVehicleConverter
|
||||||
ant.Name = "ant"
|
ant.Name = "ant"
|
||||||
|
|
@ -6470,6 +6586,11 @@ object GlobalDefinitions {
|
||||||
ant.MaxDepth = 2
|
ant.MaxDepth = 2
|
||||||
ant.UnderwaterLifespan(suffocation = 12000L, recovery = 6000L)
|
ant.UnderwaterLifespan(suffocation = 12000L, recovery = 6000L)
|
||||||
ant.Geometry = GeometryForm.representByCylinder(radius = 2.16795f, height = 2.09376f) //TODO hexahedron
|
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.Name = "ams"
|
||||||
ams.MaxHealth = 3000
|
ams.MaxHealth = 3000
|
||||||
|
|
@ -6508,6 +6629,11 @@ object GlobalDefinitions {
|
||||||
ams.MaxDepth = 3
|
ams.MaxDepth = 3
|
||||||
ams.UnderwaterLifespan(suffocation = 5000L, recovery = 5000L)
|
ams.UnderwaterLifespan(suffocation = 5000L, recovery = 5000L)
|
||||||
ams.Geometry = GeometryForm.representByCylinder(radius = 3.0117f, height = 3.39062f) //TODO hexahedron
|
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
|
val variantConverter = new VariantVehicleConverter
|
||||||
router.Name = "router"
|
router.Name = "router"
|
||||||
|
|
@ -6543,8 +6669,13 @@ object GlobalDefinitions {
|
||||||
}
|
}
|
||||||
router.DrownAtMaxDepth = true
|
router.DrownAtMaxDepth = true
|
||||||
router.MaxDepth = 2
|
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.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.Name = "switchblade"
|
||||||
switchblade.MaxHealth = 1750
|
switchblade.MaxHealth = 1750
|
||||||
|
|
@ -6585,6 +6716,11 @@ object GlobalDefinitions {
|
||||||
recovery = 5000L
|
recovery = 5000L
|
||||||
) //but the switchblade hovers over water, so ...?
|
) //but the switchblade hovers over water, so ...?
|
||||||
switchblade.Geometry = GeometryForm.representByCylinder(radius = 2.4335f, height = 2.73438f)
|
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.Name = "flail"
|
||||||
flail.MaxHealth = 2400
|
flail.MaxHealth = 2400
|
||||||
|
|
@ -6621,6 +6757,22 @@ object GlobalDefinitions {
|
||||||
flail.MaxDepth = 2
|
flail.MaxDepth = 2
|
||||||
flail.UnderwaterLifespan(suffocation = 45000L, recovery = 5000L) //but the flail hovers over water, so ...?
|
flail.UnderwaterLifespan(suffocation = 45000L, recovery = 5000L) //but the flail hovers over water, so ...?
|
||||||
flail.Geometry = GeometryForm.representByCylinder(radius = 2.1875f, height = 2.21875f)
|
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.Name = "mosquito"
|
||||||
mosquito.MaxHealth = 665
|
mosquito.MaxHealth = 665
|
||||||
|
|
@ -6654,6 +6806,11 @@ object GlobalDefinitions {
|
||||||
mosquito.DrownAtMaxDepth = true
|
mosquito.DrownAtMaxDepth = true
|
||||||
mosquito.MaxDepth = 2 //flying vehicles will automatically disable
|
mosquito.MaxDepth = 2 //flying vehicles will automatically disable
|
||||||
mosquito.Geometry = GeometryForm.representByCylinder(radius = 2.72108f, height = 2.5f)
|
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.Name = "lightgunship" // Reaver
|
||||||
lightgunship.MaxHealth = 855 // Temporary - Correct Reaver Health from pre-"Coder Madness 2" Event
|
lightgunship.MaxHealth = 855 // Temporary - Correct Reaver Health from pre-"Coder Madness 2" Event
|
||||||
|
|
@ -6688,6 +6845,11 @@ object GlobalDefinitions {
|
||||||
lightgunship.DrownAtMaxDepth = true
|
lightgunship.DrownAtMaxDepth = true
|
||||||
lightgunship.MaxDepth = 2 //flying vehicles will automatically disable
|
lightgunship.MaxDepth = 2 //flying vehicles will automatically disable
|
||||||
lightgunship.Geometry = GeometryForm.representByCylinder(radius = 2.375f, height = 1.98438f)
|
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.Name = "wasp"
|
||||||
wasp.MaxHealth = 515
|
wasp.MaxHealth = 515
|
||||||
|
|
@ -6721,6 +6883,11 @@ object GlobalDefinitions {
|
||||||
wasp.DrownAtMaxDepth = true
|
wasp.DrownAtMaxDepth = true
|
||||||
wasp.MaxDepth = 2 //flying vehicles will automatically disable
|
wasp.MaxDepth = 2 //flying vehicles will automatically disable
|
||||||
wasp.Geometry = GeometryForm.representByCylinder(radius = 2.88675f, height = 2.5f)
|
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.Name = "liberator"
|
||||||
liberator.MaxHealth = 2500
|
liberator.MaxHealth = 2500
|
||||||
|
|
@ -6729,7 +6896,7 @@ object GlobalDefinitions {
|
||||||
liberator.RepairIfDestroyed = false
|
liberator.RepairIfDestroyed = false
|
||||||
liberator.MaxShields = 500
|
liberator.MaxShields = 500
|
||||||
liberator.CanFly = true
|
liberator.CanFly = true
|
||||||
liberator.Seats += 0 -> new SeatDefinition()
|
liberator.Seats += 0 -> bailableSeat //new SeatDefinition()
|
||||||
liberator.Seats += 1 -> bailableSeat
|
liberator.Seats += 1 -> bailableSeat
|
||||||
liberator.Seats += 2 -> bailableSeat
|
liberator.Seats += 2 -> bailableSeat
|
||||||
liberator.controlledWeapons += 0 -> 3
|
liberator.controlledWeapons += 0 -> 3
|
||||||
|
|
@ -6763,6 +6930,11 @@ object GlobalDefinitions {
|
||||||
liberator.DrownAtMaxDepth = true
|
liberator.DrownAtMaxDepth = true
|
||||||
liberator.MaxDepth = 2 //flying vehicles will automatically disable
|
liberator.MaxDepth = 2 //flying vehicles will automatically disable
|
||||||
liberator.Geometry = liberatorForm
|
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.Name = "vulture"
|
||||||
vulture.MaxHealth = 2500
|
vulture.MaxHealth = 2500
|
||||||
|
|
@ -6771,7 +6943,7 @@ object GlobalDefinitions {
|
||||||
vulture.RepairIfDestroyed = false
|
vulture.RepairIfDestroyed = false
|
||||||
vulture.MaxShields = 500
|
vulture.MaxShields = 500
|
||||||
vulture.CanFly = true
|
vulture.CanFly = true
|
||||||
vulture.Seats += 0 -> new SeatDefinition()
|
vulture.Seats += 0 -> bailableSeat //new SeatDefinition()
|
||||||
vulture.Seats += 1 -> bailableSeat
|
vulture.Seats += 1 -> bailableSeat
|
||||||
vulture.Seats += 2 -> bailableSeat
|
vulture.Seats += 2 -> bailableSeat
|
||||||
vulture.controlledWeapons += 0 -> 3
|
vulture.controlledWeapons += 0 -> 3
|
||||||
|
|
@ -6806,6 +6978,11 @@ object GlobalDefinitions {
|
||||||
vulture.DrownAtMaxDepth = true
|
vulture.DrownAtMaxDepth = true
|
||||||
vulture.MaxDepth = 2 //flying vehicles will automatically disable
|
vulture.MaxDepth = 2 //flying vehicles will automatically disable
|
||||||
vulture.Geometry = liberatorForm
|
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.Name = "dropship" // Galaxy
|
||||||
dropship.MaxHealth = 5000
|
dropship.MaxHealth = 5000
|
||||||
|
|
@ -6815,7 +6992,7 @@ object GlobalDefinitions {
|
||||||
dropship.RepairIfDestroyed = false
|
dropship.RepairIfDestroyed = false
|
||||||
dropship.MaxShields = 1000
|
dropship.MaxShields = 1000
|
||||||
dropship.CanFly = true
|
dropship.CanFly = true
|
||||||
dropship.Seats += 0 -> new SeatDefinition()
|
dropship.Seats += 0 -> bailableSeat //new SeatDefinition()
|
||||||
dropship.Seats += 1 -> bailableSeat
|
dropship.Seats += 1 -> bailableSeat
|
||||||
dropship.Seats += 2 -> bailableSeat
|
dropship.Seats += 2 -> bailableSeat
|
||||||
dropship.Seats += 3 -> bailableSeat
|
dropship.Seats += 3 -> bailableSeat
|
||||||
|
|
@ -6876,6 +7053,11 @@ object GlobalDefinitions {
|
||||||
dropship.DrownAtMaxDepth = true
|
dropship.DrownAtMaxDepth = true
|
||||||
dropship.MaxDepth = 2
|
dropship.MaxDepth = 2
|
||||||
dropship.Geometry = GeometryForm.representByCylinder(radius = 10.52202f, height = 6.23438f)
|
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.Name = "galaxy_gunship"
|
||||||
galaxy_gunship.MaxHealth = 6000
|
galaxy_gunship.MaxHealth = 6000
|
||||||
|
|
@ -6885,7 +7067,7 @@ object GlobalDefinitions {
|
||||||
galaxy_gunship.RepairIfDestroyed = false
|
galaxy_gunship.RepairIfDestroyed = false
|
||||||
galaxy_gunship.MaxShields = 1200
|
galaxy_gunship.MaxShields = 1200
|
||||||
galaxy_gunship.CanFly = true
|
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 += 1 -> bailableSeat
|
||||||
galaxy_gunship.Seats += 2 -> bailableSeat
|
galaxy_gunship.Seats += 2 -> bailableSeat
|
||||||
galaxy_gunship.Seats += 3 -> bailableSeat
|
galaxy_gunship.Seats += 3 -> bailableSeat
|
||||||
|
|
@ -6930,6 +7112,11 @@ object GlobalDefinitions {
|
||||||
galaxy_gunship.DrownAtMaxDepth = true
|
galaxy_gunship.DrownAtMaxDepth = true
|
||||||
galaxy_gunship.MaxDepth = 2
|
galaxy_gunship.MaxDepth = 2
|
||||||
galaxy_gunship.Geometry = GeometryForm.representByCylinder(radius = 9.2382f, height = 5.01562f)
|
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.Name = "lodestar"
|
||||||
lodestar.MaxHealth = 5000
|
lodestar.MaxHealth = 5000
|
||||||
|
|
@ -6939,7 +7126,7 @@ object GlobalDefinitions {
|
||||||
lodestar.RepairIfDestroyed = false
|
lodestar.RepairIfDestroyed = false
|
||||||
lodestar.MaxShields = 1000
|
lodestar.MaxShields = 1000
|
||||||
lodestar.CanFly = true
|
lodestar.CanFly = true
|
||||||
lodestar.Seats += 0 -> new SeatDefinition()
|
lodestar.Seats += 0 -> bailableSeat
|
||||||
lodestar.MountPoints += 1 -> MountInfo(0)
|
lodestar.MountPoints += 1 -> MountInfo(0)
|
||||||
lodestar.MountPoints += 2 -> MountInfo(1)
|
lodestar.MountPoints += 2 -> MountInfo(1)
|
||||||
lodestar.Cargo += 1 -> new CargoDefinition()
|
lodestar.Cargo += 1 -> new CargoDefinition()
|
||||||
|
|
@ -6972,6 +7159,9 @@ object GlobalDefinitions {
|
||||||
lodestar.DrownAtMaxDepth = true
|
lodestar.DrownAtMaxDepth = true
|
||||||
lodestar.MaxDepth = 2
|
lodestar.MaxDepth = 2
|
||||||
lodestar.Geometry = GeometryForm.representByCylinder(radius = 7.8671f, height = 6.79688f) //TODO hexahedron
|
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.Name = "phantasm"
|
||||||
phantasm.MaxHealth = 2500
|
phantasm.MaxHealth = 2500
|
||||||
|
|
@ -7011,6 +7201,11 @@ object GlobalDefinitions {
|
||||||
phantasm.DrownAtMaxDepth = true
|
phantasm.DrownAtMaxDepth = true
|
||||||
phantasm.MaxDepth = 2
|
phantasm.MaxDepth = 2
|
||||||
phantasm.Geometry = GeometryForm.representByCylinder(radius = 5.2618f, height = 3f)
|
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.Name = "droppod"
|
||||||
droppod.MaxHealth = 20000
|
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.DestroyedModel = None //the adb calls out a droppod; the cyclic nature of this confounds me
|
||||||
droppod.DamageUsing = DamageCalculations.AgainstAircraft
|
droppod.DamageUsing = DamageCalculations.AgainstAircraft
|
||||||
droppod.DrownAtMaxDepth = false
|
droppod.DrownAtMaxDepth = false
|
||||||
|
droppod.mass = 2500f
|
||||||
|
|
||||||
orbital_shuttle.Name = "orbital_shuttle"
|
orbital_shuttle.Name = "orbital_shuttle"
|
||||||
orbital_shuttle.MaxHealth = 20000
|
orbital_shuttle.MaxHealth = 20000
|
||||||
|
|
@ -7059,6 +7255,7 @@ object GlobalDefinitions {
|
||||||
orbital_shuttle.DestroyedModel = None
|
orbital_shuttle.DestroyedModel = None
|
||||||
orbital_shuttle.DamageUsing = DamageCalculations.AgainstNothing
|
orbital_shuttle.DamageUsing = DamageCalculations.AgainstNothing
|
||||||
orbital_shuttle.DrownAtMaxDepth = false
|
orbital_shuttle.DrownAtMaxDepth = false
|
||||||
|
orbital_shuttle.mass = 25000f
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -7185,6 +7382,9 @@ object GlobalDefinitions {
|
||||||
Modifiers = ExplodingRadialDegrade
|
Modifiers = ExplodingRadialDegrade
|
||||||
}
|
}
|
||||||
spitfire_turret.Geometry = smallTurret
|
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.Name = "spitfire_cloaked"
|
||||||
spitfire_cloaked.Descriptor = "CloakingSpitfires"
|
spitfire_cloaked.Descriptor = "CloakingSpitfires"
|
||||||
|
|
@ -7209,6 +7409,9 @@ object GlobalDefinitions {
|
||||||
Modifiers = ExplodingRadialDegrade
|
Modifiers = ExplodingRadialDegrade
|
||||||
}
|
}
|
||||||
spitfire_cloaked.Geometry = smallTurret
|
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.Name = "spitfire_aa"
|
||||||
spitfire_aa.Descriptor = "FlakSpitfires"
|
spitfire_aa.Descriptor = "FlakSpitfires"
|
||||||
|
|
@ -7233,6 +7436,9 @@ object GlobalDefinitions {
|
||||||
Modifiers = ExplodingRadialDegrade
|
Modifiers = ExplodingRadialDegrade
|
||||||
}
|
}
|
||||||
spitfire_aa.Geometry = smallTurret
|
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.Name = "motionalarmsensor"
|
||||||
motionalarmsensor.Descriptor = "MotionSensors"
|
motionalarmsensor.Descriptor = "MotionSensors"
|
||||||
|
|
@ -7273,6 +7479,10 @@ object GlobalDefinitions {
|
||||||
Modifiers = ExplodingRadialDegrade
|
Modifiers = ExplodingRadialDegrade
|
||||||
}
|
}
|
||||||
tank_traps.Geometry = GeometryForm.representByCylinder(radius = 2.89680997f, height = 3.57812f)
|
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
|
val fieldTurretConverter = new FieldTurretConverter
|
||||||
portable_manned_turret.Name = "portable_manned_turret"
|
portable_manned_turret.Name = "portable_manned_turret"
|
||||||
|
|
@ -7303,6 +7513,9 @@ object GlobalDefinitions {
|
||||||
Modifiers = ExplodingRadialDegrade
|
Modifiers = ExplodingRadialDegrade
|
||||||
}
|
}
|
||||||
portable_manned_turret.Geometry = largeTurret
|
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.Name = "portable_manned_turret_nc"
|
||||||
portable_manned_turret_nc.Descriptor = "FieldTurrets"
|
portable_manned_turret_nc.Descriptor = "FieldTurrets"
|
||||||
|
|
@ -7332,6 +7545,9 @@ object GlobalDefinitions {
|
||||||
Modifiers = ExplodingRadialDegrade
|
Modifiers = ExplodingRadialDegrade
|
||||||
}
|
}
|
||||||
portable_manned_turret_nc.Geometry = largeTurret
|
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.Name = "portable_manned_turret_tr"
|
||||||
portable_manned_turret_tr.Descriptor = "FieldTurrets"
|
portable_manned_turret_tr.Descriptor = "FieldTurrets"
|
||||||
|
|
@ -7361,6 +7577,9 @@ object GlobalDefinitions {
|
||||||
Modifiers = ExplodingRadialDegrade
|
Modifiers = ExplodingRadialDegrade
|
||||||
}
|
}
|
||||||
portable_manned_turret_tr.Geometry = largeTurret
|
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.Name = "portable_manned_turret_vs"
|
||||||
portable_manned_turret_vs.Descriptor = "FieldTurrets"
|
portable_manned_turret_vs.Descriptor = "FieldTurrets"
|
||||||
|
|
@ -7390,6 +7609,9 @@ object GlobalDefinitions {
|
||||||
Modifiers = ExplodingRadialDegrade
|
Modifiers = ExplodingRadialDegrade
|
||||||
}
|
}
|
||||||
portable_manned_turret_vs.Geometry = largeTurret
|
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.Name = "deployable_shield_generator"
|
||||||
deployable_shield_generator.Descriptor = "ShieldGenerators"
|
deployable_shield_generator.Descriptor = "ShieldGenerators"
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||||
import net.psforever.objects.serverobject.aura.AuraContainer
|
import net.psforever.objects.serverobject.aura.AuraContainer
|
||||||
import net.psforever.objects.serverobject.environment.InteractWithEnvironment
|
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.resistance.ResistanceProfile
|
||||||
import net.psforever.objects.vital.Vitality
|
import net.psforever.objects.vital.Vitality
|
||||||
import net.psforever.objects.vital.interaction.DamageInteraction
|
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||||
|
|
@ -31,9 +32,10 @@ class Player(var avatar: Avatar)
|
||||||
with Container
|
with Container
|
||||||
with JammableUnit
|
with JammableUnit
|
||||||
with ZoneAware
|
with ZoneAware
|
||||||
with AuraContainer {
|
with AuraContainer
|
||||||
|
with MountableEntity {
|
||||||
interaction(new InteractWithEnvironment())
|
interaction(new InteractWithEnvironment())
|
||||||
interaction(new InteractWithMines(range = 10))
|
interaction(new InteractWithMinesUnlessSpectating(obj = this, range = 10))
|
||||||
|
|
||||||
private var backpack: Boolean = false
|
private var backpack: Boolean = false
|
||||||
private var released: 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,8 @@ class TurretControl(turret: TurretDeployable)
|
||||||
|
|
||||||
override protected def DestructionAwareness(target: Target, cause: DamageResult): Unit = {
|
override protected def DestructionAwareness(target: Target, cause: DamageResult): Unit = {
|
||||||
super.DestructionAwareness(target, cause)
|
super.DestructionAwareness(target, cause)
|
||||||
|
CancelJammeredSound(target)
|
||||||
|
CancelJammeredStatus(target)
|
||||||
Deployables.AnnounceDestroyDeployable(turret, None)
|
Deployables.AnnounceDestroyDeployable(turret, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import net.psforever.objects.ce.InteractWithMines
|
||||||
import net.psforever.objects.definition.{ToolDefinition, VehicleDefinition}
|
import net.psforever.objects.definition.{ToolDefinition, VehicleDefinition}
|
||||||
import net.psforever.objects.equipment.{EquipmentSize, EquipmentSlot, JammableUnit}
|
import net.psforever.objects.equipment.{EquipmentSize, EquipmentSlot, JammableUnit}
|
||||||
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem, InventoryTile}
|
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.PlanetSideServerObject
|
||||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||||
import net.psforever.objects.serverobject.aura.AuraContainer
|
import net.psforever.objects.serverobject.aura.AuraContainer
|
||||||
|
|
@ -86,7 +86,8 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
|
||||||
with JammableUnit
|
with JammableUnit
|
||||||
with CommonNtuContainer
|
with CommonNtuContainer
|
||||||
with Container
|
with Container
|
||||||
with AuraContainer {
|
with AuraContainer
|
||||||
|
with MountableEntity {
|
||||||
interaction(new InteractWithEnvironment())
|
interaction(new InteractWithEnvironment())
|
||||||
interaction(new InteractWithMines(range = 30))
|
interaction(new InteractWithMines(range = 30))
|
||||||
|
|
||||||
|
|
@ -110,11 +111,11 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
|
||||||
private var utilities: Map[Int, Utility] = Map()
|
private var utilities: Map[Int, Utility] = Map()
|
||||||
private val trunk: GridInventory = GridInventory()
|
private val trunk: GridInventory = GridInventory()
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Records the GUID of the cargo vehicle (galaxy/lodestar) this vehicle is stored in for DismountVehicleCargoMsg use
|
* 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
|
* 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 vehicleGatingManifest: Option[VehicleManifest] = None
|
||||||
private var previousVehicleGatingManifest: 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 */
|
/** How long it takes to jack the vehicle in seconds, based on the hacker's certification level */
|
||||||
def JackingDuration: Array[Int] = Definition.JackingDuration
|
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 = {
|
override def Health_=(assignHealth: Int): Int = {
|
||||||
//TODO should vehicle class enforce this?
|
//TODO should vehicle class enforce this?
|
||||||
if (!Destroyed) {
|
if (!Destroyed) {
|
||||||
|
|
@ -499,6 +484,10 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
|
||||||
|
|
||||||
def DamageModel = Definition.asInstanceOf[DamageResistanceModel]
|
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`.
|
* This is the definition entry that is used to store and unload pertinent information about the `Vehicle`.
|
||||||
* @return the vehicle's definition entry
|
* @return the vehicle's definition entry
|
||||||
|
|
|
||||||
|
|
@ -232,25 +232,13 @@ object Vehicles {
|
||||||
log.info(s"${hacker.Name} has jacked a ${target.Definition.Name}")
|
log.info(s"${hacker.Name} has jacked a ${target.Definition.Name}")
|
||||||
val zone = target.Zone
|
val zone = target.Zone
|
||||||
// Forcefully dismount any cargo
|
// Forcefully dismount any cargo
|
||||||
target.CargoHolds.values.foreach(cargoHold => {
|
target.CargoHolds.foreach { case (index, cargoHold) =>
|
||||||
cargoHold.occupant match {
|
cargoHold.occupant match {
|
||||||
case Some(cargo: Vehicle) =>
|
case Some(cargo: Vehicle) =>
|
||||||
cargo.Seats(0).occupant match {
|
cargo.Actor ! CargoBehavior.StartCargoDismounting(bailed = false)
|
||||||
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)
|
|
||||||
}
|
|
||||||
case None => ;
|
case None => ;
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
// Forcefully dismount all seated occupants from the vehicle
|
// Forcefully dismount all seated occupants from the vehicle
|
||||||
target.Seats.values.foreach(seat => {
|
target.Seats.values.foreach(seat => {
|
||||||
seat.occupant match {
|
seat.occupant match {
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import net.psforever.objects.locker.LockerContainerControl
|
||||||
import net.psforever.objects.serverobject.environment._
|
import net.psforever.objects.serverobject.environment._
|
||||||
import net.psforever.objects.serverobject.repair.Repairable
|
import net.psforever.objects.serverobject.repair.Repairable
|
||||||
import net.psforever.objects.serverobject.shuttle.OrbitalShuttlePad
|
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.environment.EnvironmentReason
|
||||||
import net.psforever.objects.vital.etc.{PainboxReason, SuicideReason}
|
import net.psforever.objects.vital.etc.{PainboxReason, SuicideReason}
|
||||||
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
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 ++ afterInventory).foreach { entry => entry.obj.Faction = player.Faction }
|
||||||
afterHolsters.foreach {
|
afterHolsters.foreach {
|
||||||
case InventoryItem(citem: ConstructionItem, _) =>
|
case InventoryItem(citem: ConstructionItem, _) =>
|
||||||
Deployables.initializeConstructionAmmoMode(player.avatar.certifications, citem)
|
Deployables.initializeConstructionItem(player.avatar.certifications, citem)
|
||||||
case _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
toDeleteOrDrop.foreach { entry => entry.obj.Faction = PlanetSideEmpire.NEUTRAL }
|
toDeleteOrDrop.foreach { entry => entry.obj.Faction = PlanetSideEmpire.NEUTRAL }
|
||||||
|
|
@ -795,7 +796,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
||||||
case _ =>
|
case _ =>
|
||||||
cause.interaction.cause.source.Aggravated.nonEmpty
|
cause.interaction.cause.source.Aggravated.nonEmpty
|
||||||
}
|
}
|
||||||
//log historical event
|
//log historical event (always)
|
||||||
target.History(cause)
|
target.History(cause)
|
||||||
//stat changes
|
//stat changes
|
||||||
if (damageToCapacitor > 0) {
|
if (damageToCapacitor > 0) {
|
||||||
|
|
@ -803,11 +804,11 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
||||||
target.Name,
|
target.Name,
|
||||||
AvatarAction.PlanetsideAttributeSelf(targetGUID, 7, target.Capacitor.toLong)
|
AvatarAction.PlanetsideAttributeSelf(targetGUID, 7, target.Capacitor.toLong)
|
||||||
)
|
)
|
||||||
announceConfrontation = true
|
announceConfrontation = true //TODO should we?
|
||||||
}
|
}
|
||||||
if (damageToStamina > 0) {
|
if (damageToStamina > 0) {
|
||||||
avatarActor ! AvatarActor.ConsumeStamina(damageToStamina)
|
avatarActor ! AvatarActor.ConsumeStamina(damageToStamina)
|
||||||
announceConfrontation = true
|
announceConfrontation = true //TODO should we?
|
||||||
}
|
}
|
||||||
if (damageToHealth > 0) {
|
if (damageToHealth > 0) {
|
||||||
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 0, health))
|
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
|
val countableDamage = damageToHealth + damageToArmor
|
||||||
if(announceConfrontation) {
|
if(announceConfrontation) {
|
||||||
if (!aggravated) {
|
if (aggravated) {
|
||||||
|
events ! AvatarServiceMessage(
|
||||||
|
zoneId,
|
||||||
|
AvatarAction.SendResponse(Service.defaultPlayerGUID, AggravatedDamageMessage(targetGUID, countableDamage))
|
||||||
|
)
|
||||||
|
} else {
|
||||||
//activity on map
|
//activity on map
|
||||||
zone.Activity ! Zone.HotSpot.Activity(cause)
|
zone.Activity ! Zone.HotSpot.Activity(cause)
|
||||||
//alert to damage source
|
//alert to damage source
|
||||||
cause.adversarial match {
|
cause.adversarial match {
|
||||||
case Some(adversarial) =>
|
case Some(adversarial) =>
|
||||||
adversarial.attacker match {
|
adversarial.attacker match {
|
||||||
case pSource : PlayerSource => //player damage
|
case pSource: PlayerSource => //player damage
|
||||||
val name = pSource.Name
|
val name = pSource.Name
|
||||||
zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match {
|
zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match {
|
||||||
case Some(tplayer) =>
|
case Some(tplayer) =>
|
||||||
|
|
@ -855,6 +861,11 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
||||||
target.Name,
|
target.Name,
|
||||||
AvatarAction.EnvironmentalDamage(target.GUID, o.entity.GUID, countableDamage)
|
AvatarAction.EnvironmentalDamage(target.GUID, o.entity.GUID, countableDamage)
|
||||||
)
|
)
|
||||||
|
case _: CollisionReason =>
|
||||||
|
events ! AvatarServiceMessage(
|
||||||
|
zoneId,
|
||||||
|
AvatarAction.SendResponse(Service.defaultPlayerGUID, AggravatedDamageMessage(targetGUID, countableDamage))
|
||||||
|
)
|
||||||
case _ =>
|
case _ =>
|
||||||
zone.AvatarEvents ! AvatarServiceMessage(
|
zone.AvatarEvents ! AvatarServiceMessage(
|
||||||
target.Name,
|
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
|
//can not preserve ammo type in construction tool packets
|
||||||
citem.resetAmmoTypes()
|
citem.resetAmmoTypes()
|
||||||
}
|
}
|
||||||
Deployables.initializeConstructionAmmoMode(player.avatar.certifications, citem)
|
Deployables.initializeConstructionItem(player.avatar.certifications, citem)
|
||||||
|
|
||||||
case _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ final case class DeployableSource(
|
||||||
faction: PlanetSideEmpire.Value,
|
faction: PlanetSideEmpire.Value,
|
||||||
health: Int,
|
health: Int,
|
||||||
shields: Int,
|
shields: Int,
|
||||||
ownerName: String,
|
owner: SourceEntry,
|
||||||
position: Vector3,
|
position: Vector3,
|
||||||
orientation: Vector3
|
orientation: Vector3
|
||||||
) extends SourceEntry {
|
) extends SourceEntry {
|
||||||
|
|
@ -20,7 +20,7 @@ final case class DeployableSource(
|
||||||
def Definition: ObjectDefinition with DeployableDefinition = obj_def
|
def Definition: ObjectDefinition with DeployableDefinition = obj_def
|
||||||
def Health = health
|
def Health = health
|
||||||
def Shields = shields
|
def Shields = shields
|
||||||
def OwnerName = ownerName
|
def OwnerName = owner.Name
|
||||||
def Position = position
|
def Position = position
|
||||||
def Orientation = orientation
|
def Orientation = orientation
|
||||||
def Velocity = None
|
def Velocity = None
|
||||||
|
|
@ -29,12 +29,19 @@ final case class DeployableSource(
|
||||||
|
|
||||||
object DeployableSource {
|
object DeployableSource {
|
||||||
def apply(obj: Deployable): 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(
|
DeployableSource(
|
||||||
obj.Definition,
|
obj.Definition,
|
||||||
obj.Faction,
|
obj.Faction,
|
||||||
obj.Health,
|
obj.Health,
|
||||||
obj.Shields,
|
obj.Shields,
|
||||||
obj.OwnerName.getOrElse(""),
|
ownerSource,
|
||||||
obj.Position,
|
obj.Position,
|
||||||
obj.Orientation
|
obj.Orientation
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ final case class VehicleSource(
|
||||||
position: Vector3,
|
position: Vector3,
|
||||||
orientation: Vector3,
|
orientation: Vector3,
|
||||||
velocity: Option[Vector3],
|
velocity: Option[Vector3],
|
||||||
|
occupants: List[SourceEntry],
|
||||||
modifiers: ResistanceProfile
|
modifiers: ResistanceProfile
|
||||||
) extends SourceEntry {
|
) extends SourceEntry {
|
||||||
override def Name = SourceEntry.NameFormat(obj_def.Name)
|
override def Name = SourceEntry.NameFormat(obj_def.Name)
|
||||||
|
|
@ -37,6 +38,12 @@ object VehicleSource {
|
||||||
obj.Position,
|
obj.Position,
|
||||||
obj.Orientation,
|
obj.Orientation,
|
||||||
obj.Velocity,
|
obj.Velocity,
|
||||||
|
obj.Seats.values.map { seat =>
|
||||||
|
seat.occupant match {
|
||||||
|
case Some(p) => PlayerSource(p)
|
||||||
|
case _ => SourceEntry.None
|
||||||
|
}
|
||||||
|
}.toList,
|
||||||
obj.Definition.asInstanceOf[ResistanceProfile]
|
obj.Definition.asInstanceOf[ResistanceProfile]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import net.psforever.objects.definition.converter.SmallDeployableConverter
|
||||||
import net.psforever.objects.vital.damage.DamageCalculations
|
import net.psforever.objects.vital.damage.DamageCalculations
|
||||||
import net.psforever.objects.vital.resistance.ResistanceProfileMutators
|
import net.psforever.objects.vital.resistance.ResistanceProfileMutators
|
||||||
import net.psforever.objects.vital.resolution.DamageResistanceModel
|
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._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
|
|
@ -60,6 +60,7 @@ abstract class DeployableDefinition(objectId: Int)
|
||||||
ResistUsing = NoResistanceSelection
|
ResistUsing = NoResistanceSelection
|
||||||
Packet = new SmallDeployableConverter
|
Packet = new SmallDeployableConverter
|
||||||
registerAs = "deployables"
|
registerAs = "deployables"
|
||||||
|
collision.xy = new CollisionXYData(List((0f, 100)))
|
||||||
|
|
||||||
def Item: DeployedItem.Value = item
|
def Item: DeployedItem.Value = item
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ class ExoSuitDefinition(private val suitType: ExoSuitType.Value)
|
||||||
protected var capacitorRechargeDelayMillis: Int = 0
|
protected var capacitorRechargeDelayMillis: Int = 0
|
||||||
protected var capacitorRechargePerSecond: Int = 0
|
protected var capacitorRechargePerSecond: Int = 0
|
||||||
protected var capacitorDrainPerSecond: Int = 0
|
protected var capacitorDrainPerSecond: Int = 0
|
||||||
|
val collision: ExosuitCollisionData = new ExosuitCollisionData()
|
||||||
Name = "exo-suit"
|
Name = "exo-suit"
|
||||||
DamageUsing = DamageCalculations.AgainstExoSuit
|
DamageUsing = DamageCalculations.AgainstExoSuit
|
||||||
ResistUsing = StandardInfantryResistance
|
ResistUsing = StandardInfantryResistance
|
||||||
|
|
|
||||||
|
|
@ -101,5 +101,15 @@ abstract class ObjectDefinition(private val objectId: Int) extends BasicDefiniti
|
||||||
Geometry
|
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
|
def ObjectId: Int = objectId
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -197,6 +197,8 @@ class VehicleDefinition(objectId: Int)
|
||||||
obj.Actor ! akka.actor.PoisonPill
|
obj.Actor ! akka.actor.PoisonPill
|
||||||
obj.Actor = Default.Actor
|
obj.Actor = Default.Actor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val collision: AdvancedCollisionData = new AdvancedCollisionData()
|
||||||
}
|
}
|
||||||
|
|
||||||
object VehicleDefinition {
|
object VehicleDefinition {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import akka.actor.Actor
|
||||||
import net.psforever.objects.{Vehicle, Vehicles}
|
import net.psforever.objects.{Vehicle, Vehicles}
|
||||||
import net.psforever.objects.equipment.JammableUnit
|
import net.psforever.objects.equipment.JammableUnit
|
||||||
import net.psforever.objects.serverobject.damage.Damageable.Target
|
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.interaction.DamageResult
|
||||||
import net.psforever.objects.vital.resolution.ResolutionCalculations
|
import net.psforever.objects.vital.resolution.ResolutionCalculations
|
||||||
import net.psforever.services.Service
|
import net.psforever.services.Service
|
||||||
|
|
@ -103,13 +104,13 @@ trait DamageableVehicle
|
||||||
case _ => (0, 0, 0)
|
case _ => (0, 0, 0)
|
||||||
}
|
}
|
||||||
var announceConfrontation: Boolean = reportDamageToVehicle || totalDamage > 0
|
var announceConfrontation: Boolean = reportDamageToVehicle || totalDamage > 0
|
||||||
val aggravated = TryAggravationEffectActivate(cause) match {
|
val showAsAggravated = (TryAggravationEffectActivate(cause) match {
|
||||||
case Some(_) =>
|
case Some(_) =>
|
||||||
announceConfrontation = true
|
announceConfrontation = true
|
||||||
false
|
false
|
||||||
case _ =>
|
case _ =>
|
||||||
cause.interaction.cause.source.Aggravated.nonEmpty
|
cause.interaction.cause.source.Aggravated.nonEmpty
|
||||||
}
|
}) || cause.interaction.cause.resolution == DamageResolution.Collision
|
||||||
reportDamageToVehicle = false
|
reportDamageToVehicle = false
|
||||||
|
|
||||||
if (obj.MountedIn.nonEmpty) {
|
if (obj.MountedIn.nonEmpty) {
|
||||||
|
|
@ -139,7 +140,7 @@ trait DamageableVehicle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (announceConfrontation) {
|
if (announceConfrontation) {
|
||||||
if (aggravated) {
|
if (showAsAggravated) {
|
||||||
val msg = VehicleAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(totalDamage, Vector3.Zero))
|
val msg = VehicleAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(totalDamage, Vector3.Zero))
|
||||||
obj.Seats.values
|
obj.Seats.values
|
||||||
.collect { case seat if seat.occupant.nonEmpty => seat.occupant.get.Name }
|
.collect { case seat if seat.occupant.nonEmpty => seat.occupant.get.Name }
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package net.psforever.objects.serverobject.mount
|
||||||
|
|
||||||
import akka.actor.ActorRef
|
import akka.actor.ActorRef
|
||||||
import net.psforever.objects.Player
|
import net.psforever.objects.Player
|
||||||
|
import net.psforever.types.BailType
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
|
|
||||||
|
|
@ -97,7 +98,11 @@ object Mountable {
|
||||||
* @param player the player who sent this request message
|
* @param player the player who sent this request message
|
||||||
* @param seat_num the seat index
|
* @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.
|
* A basic `Trait` connecting all of the actionable `Mountable` response messages.
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import net.psforever.objects.Player
|
||||||
import net.psforever.objects.entity.WorldEntity
|
import net.psforever.objects.entity.WorldEntity
|
||||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||||
import net.psforever.objects.serverobject.hackable.Hackable
|
import net.psforever.objects.serverobject.hackable.Hackable
|
||||||
|
import net.psforever.types.BailType
|
||||||
|
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
|
|
||||||
|
|
@ -82,9 +83,9 @@ trait MountableBehavior {
|
||||||
* @see `Mountable`
|
* @see `Mountable`
|
||||||
*/
|
*/
|
||||||
val dismountBehavior: Receive = {
|
val dismountBehavior: Receive = {
|
||||||
case Mountable.TryDismount(user, seat_number) =>
|
case Mountable.TryDismount(user, seat_number, bail_type) =>
|
||||||
val obj = MountableObject
|
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
|
user.VehicleSeated = None
|
||||||
obj.Zone.actor ! ZoneActor.AddToBlockMap(user, obj.Position)
|
obj.Zone.actor ! ZoneActor.AddToBlockMap(user, obj.Position)
|
||||||
sender() ! Mountable.MountMessages(
|
sender() ! Mountable.MountMessages(
|
||||||
|
|
@ -112,11 +113,12 @@ trait MountableBehavior {
|
||||||
private def tryDismount(
|
private def tryDismount(
|
||||||
obj: Mountable,
|
obj: Mountable,
|
||||||
seatNumber: Int,
|
seatNumber: Int,
|
||||||
user: Player
|
user: Player,
|
||||||
|
bailType: BailType.Value
|
||||||
): Boolean = {
|
): Boolean = {
|
||||||
obj.Seats.get(seatNumber) match {
|
obj.Seats.get(seatNumber) match {
|
||||||
case Some(seat) => seat.unmount(user).isEmpty
|
case Some(seat) => seat.unmount(user, bailType).isEmpty
|
||||||
case _ => false
|
case _ => false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
// Copyright (c) 2021 PSForever
|
// Copyright (c) 2021 PSForever
|
||||||
package net.psforever.objects.serverobject.mount
|
package net.psforever.objects.serverobject.mount
|
||||||
|
|
||||||
trait MountableSpace[A] {
|
import net.psforever.types.BailType
|
||||||
|
|
||||||
|
trait MountableSpace[A <: MountableEntity] {
|
||||||
private var _occupant: Option[A] = None
|
private var _occupant: Option[A] = None
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -53,6 +55,7 @@ trait MountableSpace[A] {
|
||||||
target match {
|
target match {
|
||||||
case Some(p) if testToMount(p) =>
|
case Some(p) if testToMount(p) =>
|
||||||
_occupant = target
|
_occupant = target
|
||||||
|
p.BailProtection = false
|
||||||
target
|
target
|
||||||
case _ =>
|
case _ =>
|
||||||
occupant
|
occupant
|
||||||
|
|
@ -63,7 +66,7 @@ trait MountableSpace[A] {
|
||||||
* Tests whether the target is allowed to be mounted.
|
* Tests whether the target is allowed to be mounted.
|
||||||
* @see `MountableSpace[A].canBeOccupiedBy(A)`
|
* @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.
|
* 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.
|
* 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 {
|
target match {
|
||||||
case Some(p) if testToUnmount(p) =>
|
case Some(p) if testToUnmount(p) =>
|
||||||
_occupant = None
|
_occupant = None
|
||||||
|
p.BailProtection = bailable && (bailType == BailType.Bailed || bailType == BailType.Kicked)
|
||||||
None
|
None
|
||||||
case _ =>
|
case _ =>
|
||||||
occupant
|
occupant
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,5 @@ import net.psforever.objects.Vehicle
|
||||||
import net.psforever.objects.serverobject.mount.{MountableSpace, MountableSpaceDefinition}
|
import net.psforever.objects.serverobject.mount.{MountableSpace, MountableSpaceDefinition}
|
||||||
|
|
||||||
class Cargo(private val cdef: MountableSpaceDefinition[Vehicle]) extends MountableSpace[Vehicle] {
|
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
|
def definition: MountableSpaceDefinition[Vehicle] = cdef
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,12 @@
|
||||||
// Copyright (c) 2020 PSForever
|
// Copyright (c) 2020 PSForever
|
||||||
package net.psforever.objects.vehicles
|
package net.psforever.objects.vehicles
|
||||||
|
|
||||||
import akka.actor.{Actor, Cancellable}
|
import akka.actor.Actor
|
||||||
import net.psforever.actors.zone.ZoneActor
|
|
||||||
import net.psforever.objects.zones.Zone
|
|
||||||
import net.psforever.objects._
|
import net.psforever.objects._
|
||||||
import net.psforever.objects.vehicles.CargoBehavior.{CheckCargoDismount, CheckCargoMounting}
|
import net.psforever.types.PlanetSideGUID
|
||||||
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._
|
|
||||||
|
|
||||||
trait CargoBehavior {
|
trait CargoBehavior {
|
||||||
_: Actor =>
|
_: 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 */
|
/* gate-keep mounting behavior so that unit does not try to dismount as cargo, or mount different vehicle */
|
||||||
private var isMounting: Option[PlanetSideGUID] = None
|
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 */
|
/* 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 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 = {
|
val cargoBehavior: Receive = {
|
||||||
case CheckCargoMounting(carrier_guid, mountPoint, iteration) =>
|
case CargoBehavior.StartCargoMounting(carrier_guid, mountPoint) =>
|
||||||
val obj = CargoObject
|
startCargoMounting(carrier_guid, mountPoint)
|
||||||
if (
|
|
||||||
(isMounting.isEmpty || isMounting.contains(carrier_guid)) && isDismounting.isEmpty &&
|
case CargoBehavior.StartCargoDismounting(bailed) =>
|
||||||
CargoBehavior.HandleCheckCargoMounting(obj.Zone, carrier_guid, obj.GUID, obj, mountPoint, iteration)
|
startCargoDismounting(bailed)
|
||||||
) {
|
|
||||||
if (iteration == 0) {
|
case CargoBehavior.EndCargoMounting(carrier_guid) =>
|
||||||
//open the cargo bay door
|
if (isMounting.contains(carrier_guid)) {
|
||||||
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 {
|
|
||||||
isMounting = None
|
isMounting = None
|
||||||
}
|
}
|
||||||
|
|
||||||
case CheckCargoDismount(carrier_guid, mountPoint, iteration) =>
|
case CargoBehavior.EndCargoDismounting(carrier_guid) =>
|
||||||
val obj = CargoObject
|
if (isDismounting.contains(carrier_guid)) {
|
||||||
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 {
|
|
||||||
isDismounting = None
|
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 {
|
object CargoBehavior {
|
||||||
private val log = org.log4s.getLogger("CargoBehavior")
|
final case class StartCargoMounting(cargo_guid: PlanetSideGUID, cargo_mountpoint: Int)
|
||||||
|
final case class StartCargoDismounting(bailed: Boolean)
|
||||||
final case class CheckCargoMounting(carrier_guid: PlanetSideGUID, cargo_mountpoint: Int, iteration: Int)
|
final case class EndCargoMounting(carrier_guid: PlanetSideGUID)
|
||||||
final case class CheckCargoDismount(carrier_guid: PlanetSideGUID, cargo_mountpoint: Int, iteration: Int)
|
final case class EndCargoDismounting(carrier_guid: PlanetSideGUID)
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
// Copyright (c) 2017 PSForever
|
|
||||||
package net.psforever.objects.vehicles
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An `Enumeration` of exo-suit-based mount access restrictions.<br>
|
|
||||||
* <br>
|
|
||||||
* The default value is `NoMax` as that is the most common mount.
|
|
||||||
* `NoReinforcedOrMax` is next most common.
|
|
||||||
* `MaxOnly` is a rare mount restriction found in pairs on Galaxies and on the large "Ground Transport" vehicles.
|
|
||||||
*/
|
|
||||||
object CargoVehicleRestriction extends Enumeration {
|
|
||||||
type Type = Value
|
|
||||||
|
|
||||||
val Small, Large = Value
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -3,7 +3,7 @@ package net.psforever.objects.vehicles.control
|
||||||
|
|
||||||
import net.psforever.objects._
|
import net.psforever.objects._
|
||||||
import net.psforever.objects.serverobject.damage.{Damageable, DamageableVehicle}
|
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
|
import net.psforever.objects.vital.interaction.DamageResult
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -13,10 +13,15 @@ import net.psforever.objects.vital.interaction.DamageResult
|
||||||
*/
|
*/
|
||||||
class CargoCarrierControl(vehicle: Vehicle)
|
class CargoCarrierControl(vehicle: Vehicle)
|
||||||
extends VehicleControl(vehicle)
|
extends VehicleControl(vehicle)
|
||||||
with CargoBehavior {
|
with CarrierBehavior {
|
||||||
def CargoObject = vehicle
|
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.
|
* 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 = {
|
override def PrepareForDisabled(kickPassengers: Boolean) : Unit = {
|
||||||
//abandon all cargo
|
//abandon all cargo
|
||||||
vehicle.CargoHolds.values
|
vehicle.CargoHolds.collect {
|
||||||
.collect {
|
case (index, hold : Cargo) if hold.isOccupied =>
|
||||||
case hold if hold.isOccupied =>
|
val cargo = hold.occupant.get
|
||||||
val cargo = hold.occupant.get
|
checkCargoDismount(cargo.GUID, index, iteration = 0, bailed = false)
|
||||||
CargoBehavior.HandleVehicleCargoDismount(
|
super.PrepareForDisabled(kickPassengers)
|
||||||
cargo.GUID,
|
}
|
||||||
cargo,
|
|
||||||
vehicle.GUID,
|
|
||||||
vehicle,
|
|
||||||
bailed = false,
|
|
||||||
requestedByPassenger = false,
|
|
||||||
kicked = false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
super.PrepareForDisabled(kickPassengers)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ class DeployingVehicleControl(vehicle: Vehicle)
|
||||||
case msg : Deployment.TryUndeploy =>
|
case msg : Deployment.TryUndeploy =>
|
||||||
deployBehavior.apply(msg)
|
deployBehavior.apply(msg)
|
||||||
|
|
||||||
case msg @ Mountable.TryDismount(_, seat_num) =>
|
case msg @ Mountable.TryDismount(_, seat_num, _) =>
|
||||||
dismountBehavior.apply(msg)
|
dismountBehavior.apply(msg)
|
||||||
dismountCleanup(seat_num)
|
dismountCleanup(seat_num)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import net.psforever.objects.serverobject.hackable.GenericHackables
|
||||||
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
|
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
|
||||||
import net.psforever.objects.serverobject.repair.RepairableVehicle
|
import net.psforever.objects.serverobject.repair.RepairableVehicle
|
||||||
import net.psforever.objects.serverobject.terminals.Terminal
|
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.interaction.{DamageInteraction, DamageResult}
|
||||||
import net.psforever.objects.vital.VehicleShieldCharge
|
import net.psforever.objects.vital.VehicleShieldCharge
|
||||||
import net.psforever.objects.vital.environment.EnvironmentReason
|
import net.psforever.objects.vital.environment.EnvironmentReason
|
||||||
|
|
@ -51,7 +51,8 @@ class VehicleControl(vehicle: Vehicle)
|
||||||
with JammableMountedWeapons
|
with JammableMountedWeapons
|
||||||
with ContainableBehavior
|
with ContainableBehavior
|
||||||
with AggravatedBehavior
|
with AggravatedBehavior
|
||||||
with RespondsToZoneEnvironment {
|
with RespondsToZoneEnvironment
|
||||||
|
with CargoBehavior {
|
||||||
//make control actors belonging to utilities when making control actor belonging to vehicle
|
//make control actors belonging to utilities when making control actor belonging to vehicle
|
||||||
vehicle.Utilities.foreach { case (_, util) => util.Setup }
|
vehicle.Utilities.foreach { case (_, util) => util.Setup }
|
||||||
|
|
||||||
|
|
@ -68,6 +69,9 @@ class VehicleControl(vehicle: Vehicle)
|
||||||
def ContainerObject = vehicle
|
def ContainerObject = vehicle
|
||||||
|
|
||||||
def InteractiveObject = vehicle
|
def InteractiveObject = vehicle
|
||||||
|
|
||||||
|
def CargoObject = vehicle
|
||||||
|
|
||||||
SetInteraction(EnvironmentAttribute.Water, doInteractingWithWater)
|
SetInteraction(EnvironmentAttribute.Water, doInteractingWithWater)
|
||||||
SetInteraction(EnvironmentAttribute.Lava, doInteractingWithLava)
|
SetInteraction(EnvironmentAttribute.Lava, doInteractingWithLava)
|
||||||
SetInteraction(EnvironmentAttribute.Death, doInteractingWithDeath)
|
SetInteraction(EnvironmentAttribute.Death, doInteractingWithDeath)
|
||||||
|
|
@ -94,6 +98,7 @@ class VehicleControl(vehicle: Vehicle)
|
||||||
util().Actor = Default.Actor
|
util().Actor = Default.Actor
|
||||||
}
|
}
|
||||||
recoverFromEnvironmentInteracting()
|
recoverFromEnvironmentInteracting()
|
||||||
|
endAllCargoOperations()
|
||||||
}
|
}
|
||||||
|
|
||||||
def commonEnabledBehavior: Receive = checkBehavior
|
def commonEnabledBehavior: Receive = checkBehavior
|
||||||
|
|
@ -102,6 +107,7 @@ class VehicleControl(vehicle: Vehicle)
|
||||||
.orElse(canBeRepairedByNanoDispenser)
|
.orElse(canBeRepairedByNanoDispenser)
|
||||||
.orElse(containerBehavior)
|
.orElse(containerBehavior)
|
||||||
.orElse(environmentBehavior)
|
.orElse(environmentBehavior)
|
||||||
|
.orElse(cargoBehavior)
|
||||||
.orElse {
|
.orElse {
|
||||||
case Vehicle.Ownership(None) =>
|
case Vehicle.Ownership(None) =>
|
||||||
LoseOwnership()
|
LoseOwnership()
|
||||||
|
|
@ -113,7 +119,7 @@ class VehicleControl(vehicle: Vehicle)
|
||||||
mountBehavior.apply(msg)
|
mountBehavior.apply(msg)
|
||||||
mountCleanup(mount_point, player)
|
mountCleanup(mount_point, player)
|
||||||
|
|
||||||
case msg @ Mountable.TryDismount(_, seat_num) =>
|
case msg @ Mountable.TryDismount(_, seat_num, _) =>
|
||||||
dismountBehavior.apply(msg)
|
dismountBehavior.apply(msg)
|
||||||
dismountCleanup(seat_num)
|
dismountCleanup(seat_num)
|
||||||
|
|
||||||
|
|
@ -247,7 +253,7 @@ class VehicleControl(vehicle: Vehicle)
|
||||||
|
|
||||||
def commonDisabledBehavior: Receive = checkBehavior
|
def commonDisabledBehavior: Receive = checkBehavior
|
||||||
.orElse {
|
.orElse {
|
||||||
case msg @ Mountable.TryDismount(_, seat_num) =>
|
case msg @ Mountable.TryDismount(_, seat_num, _) =>
|
||||||
dismountBehavior.apply(msg)
|
dismountBehavior.apply(msg)
|
||||||
dismountCleanup(seat_num)
|
dismountCleanup(seat_num)
|
||||||
|
|
||||||
|
|
@ -372,13 +378,7 @@ class VehicleControl(vehicle: Vehicle)
|
||||||
//escape being someone else's cargo
|
//escape being someone else's cargo
|
||||||
vehicle.MountedIn match {
|
vehicle.MountedIn match {
|
||||||
case Some(_) =>
|
case Some(_) =>
|
||||||
CargoBehavior.HandleVehicleCargoDismount(
|
startCargoDismounting(bailed = false)
|
||||||
zone,
|
|
||||||
guid,
|
|
||||||
bailed = false,
|
|
||||||
requestedByPassenger = false,
|
|
||||||
kicked = false
|
|
||||||
)
|
|
||||||
case _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
if (!vehicle.isFlying || kickPassengers) {
|
if (!vehicle.isFlying || kickPassengers) {
|
||||||
|
|
@ -386,13 +386,13 @@ class VehicleControl(vehicle: Vehicle)
|
||||||
vehicle.Seats.values.foreach { seat =>
|
vehicle.Seats.values.foreach { seat =>
|
||||||
seat.occupant match {
|
seat.occupant match {
|
||||||
case Some(player) =>
|
case Some(player) =>
|
||||||
seat.unmount(player)
|
seat.unmount(player, BailType.Kicked)
|
||||||
player.VehicleSeated = None
|
player.VehicleSeated = None
|
||||||
if (player.isAlive) {
|
if (player.isAlive) {
|
||||||
zone.actor ! ZoneActor.AddToBlockMap(player, vehicle.Position)
|
zone.actor ! ZoneActor.AddToBlockMap(player, vehicle.Position)
|
||||||
}
|
}
|
||||||
if (player.HasGUID) {
|
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 => ;
|
case None => ;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)))
|
||||||
|
}
|
||||||
|
|
@ -160,7 +160,7 @@ trait VitalityDefinition extends DamageModifiers {
|
||||||
var explodes: Boolean = false
|
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
|
var innateDamage: Option[DamageWithPosition] = None
|
||||||
|
|
||||||
|
|
@ -168,4 +168,8 @@ trait VitalityDefinition extends DamageModifiers {
|
||||||
innateDamage = Some(combustion)
|
innateDamage = Some(combustion)
|
||||||
innateDamage
|
innateDamage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val collision: CollisionData = new CollisionData()
|
||||||
|
|
||||||
|
var mass: Float = 1f
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ object DamageResolution extends Enumeration {
|
||||||
AggravatedSplashBurn, //continuous splashed aggravated damage
|
AggravatedSplashBurn, //continuous splashed aggravated damage
|
||||||
Explosion, //area of effect damage caused by an internal mechanism; unrelated to Splash
|
Explosion, //area of effect damage caused by an internal mechanism; unrelated to Splash
|
||||||
Environmental, //died to environmental causes
|
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
|
= Value
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,38 +1,103 @@
|
||||||
// Copyright (c) 2020 PSForever
|
// Copyright (c) 2020 PSForever
|
||||||
package net.psforever.objects.vital.collision
|
package net.psforever.objects.vital.collision
|
||||||
|
|
||||||
import net.psforever.objects.ballistics.SourceEntry
|
import net.psforever.objects.ballistics.{DeployableSource, SourceEntry, VehicleSource}
|
||||||
import net.psforever.objects.vital.base.{DamageReason, DamageResolution}
|
import net.psforever.objects.vital.base.{DamageModifiers, DamageReason, DamageResolution, DamageType}
|
||||||
import net.psforever.objects.vital.prop.DamageProperties
|
import net.psforever.objects.vital.prop.DamageProperties
|
||||||
import net.psforever.objects.vital.resolution.DamageAndResistance
|
import net.psforever.objects.vital.resolution.DamageAndResistance
|
||||||
|
import net.psforever.types.Vector3
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wrapper for a "damage source" in damage calculations
|
* Common base for reporting damage for reasons of collisions.
|
||||||
* 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
|
|
||||||
*/
|
*/
|
||||||
final case class AdversarialCollisionReason(source: DamageProperties) extends DamageReason {
|
trait CausedByColliding
|
||||||
def resolution: DamageResolution.Value = DamageResolution.Unresolved
|
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
|
* A wrapper for a "damage source" in damage calculations that explains a collision.
|
||||||
* that parameterizes information necessary to explain a collision.
|
* @param velocity how fast the target is moving prior to the collision
|
||||||
* @param source na
|
* @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 {
|
final case class CollisionReason(
|
||||||
def resolution: DamageResolution.Value = DamageResolution.Unresolved
|
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 unstructuredModifiers: List[DamageModifiers.Mod] = List(
|
||||||
|
GroundImpact,
|
||||||
override def adversary : Option[SourceEntry] = None
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import net.psforever.objects.serverobject.environment.PieceOfEnvironment
|
||||||
import net.psforever.objects.serverobject.structures.FoundationBuilder
|
import net.psforever.objects.serverobject.structures.FoundationBuilder
|
||||||
import net.psforever.objects.serverobject.zipline.ZipLinePath
|
import net.psforever.objects.serverobject.zipline.ZipLinePath
|
||||||
import net.psforever.objects.serverobject.{PlanetSideServerObject, ServerObjectBuilder}
|
import net.psforever.objects.serverobject.{PlanetSideServerObject, ServerObjectBuilder}
|
||||||
|
import net.psforever.types.Vector3
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The fixed instantiation and relation of a series of server objects.<br>
|
* The fixed instantiation and relation of a series of server objects.<br>
|
||||||
|
|
@ -129,4 +130,8 @@ class ZoneMap(val name: String) {
|
||||||
lattice = lattice ++ Set((source, target))
|
lattice = lattice ++ Set((source, target))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def areValidCoordinates(coordinates: Vector3): Boolean = {
|
||||||
|
coordinates.x >= 0 && coordinates.x <= scale.width &&
|
||||||
|
coordinates.y >= 0 && coordinates.y <= scale.height
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -230,7 +230,7 @@ class BlockMap(fullMapWidth: Int, fullMapHeight: Int, desiredSpanSize: Int) {
|
||||||
def move(target: BlockMapEntity, toPosition: Vector3): SectorPopulation = {
|
def move(target: BlockMapEntity, toPosition: Vector3): SectorPopulation = {
|
||||||
target.blockMapEntry match {
|
target.blockMapEntry match {
|
||||||
case Some(entry) => move(target, toPosition, entry.coords, entry.range)
|
case Some(entry) => move(target, toPosition, entry.coords, entry.range)
|
||||||
case None => SectorGroup(Nil)
|
case _ => SectorGroup(Nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,11 +82,15 @@ object BlockMapEntity {
|
||||||
* To properly update the range, perform a proper update.)
|
* To properly update the range, perform a proper update.)
|
||||||
* @param target the entity on the blockmap
|
* @param target the entity on the blockmap
|
||||||
* @param newCoords the world coordinates of the entity, the position to which it is moving / being moved
|
* @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 = {
|
private def updateBlockMap(target: BlockMapEntity, newCoords: Vector3): Boolean = {
|
||||||
val oldEntry = target.blockMapEntry.get
|
target.blockMapEntry match {
|
||||||
target.blockMapEntry = Some(BlockMapEntry(newCoords, oldEntry.range, oldEntry.sectors))
|
case Some(oldEntry) =>
|
||||||
true
|
target.blockMapEntry = Some(BlockMapEntry(newCoords, oldEntry.range, oldEntry.sectors))
|
||||||
|
true
|
||||||
|
case None =>
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,11 +33,11 @@ object DamageWithPositionMessage extends Marshallable[DamageWithPositionMessage]
|
||||||
).xmap[DamageWithPositionMessage] (
|
).xmap[DamageWithPositionMessage] (
|
||||||
{
|
{
|
||||||
case unk :: pos :: HNil =>
|
case unk :: pos :: HNil =>
|
||||||
DamageWithPositionMessage(math.max(0, math.min(unk, 255)), pos)
|
DamageWithPositionMessage(unk, pos)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
case DamageWithPositionMessage(unk, pos) =>
|
case DamageWithPositionMessage(unk, pos) =>
|
||||||
unk :: pos :: HNil
|
math.max(0, math.min(unk, 255)) :: pos :: HNil
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,15 @@ import scodec.Codec
|
||||||
import scodec.codecs._
|
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
|
* Request dismount of one vehicle (cargo) that is being ferried by another vehicle (carrier).
|
||||||
* @param player_guid // GUID of the player that is rqeuesting dismount
|
* The carrier has what is called a "cargo bay" which is where the cargo is being stored for ferrying.
|
||||||
* @param vehicle_guid GUID of the vehicle that is requesting dismount
|
* @param player_guid GUID of the player that is rqeuesting dismount;
|
||||||
* @param bailed If the vehicle bailed out of the cargo vehicle
|
* when kicked by carrier driver, player_guid will be PlanetSideGUID(0);
|
||||||
* @param requestedByPassenger If a passenger of the vehicle in the cargo bay requests dismount this bit will be set
|
* when exiting of the cargo vehicle driver's own accord, player_guid will be the cargo vehicle driver
|
||||||
* @param kicked If the vehicle was kicked by the cargo vehicle pilot
|
* @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(
|
final case class DismountVehicleCargoMsg(
|
||||||
player_guid: PlanetSideGUID,
|
player_guid: PlanetSideGUID,
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,27 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package net.psforever.packet.game
|
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 net.psforever.types.{PlanetSideGUID, Vector3}
|
||||||
|
import scodec.Attempt.Successful
|
||||||
import scodec.Codec
|
import scodec.Codec
|
||||||
import scodec.codecs._
|
import scodec.codecs._
|
||||||
|
import shapeless.{::, HNil}
|
||||||
|
|
||||||
|
sealed abstract class CollisionIs(val value: Int) extends IntEnumEntry
|
||||||
|
|
||||||
|
object CollisionIs extends IntEnum[CollisionIs] {
|
||||||
|
val values = findValues
|
||||||
|
|
||||||
|
case object OfGroundVehicle extends CollisionIs(value = 0)
|
||||||
|
|
||||||
|
case object OfAircraft extends CollisionIs(value = 1)
|
||||||
|
|
||||||
|
case object OfInfantry extends CollisionIs(value = 2)
|
||||||
|
|
||||||
|
case object BetweenThings extends CollisionIs(value = 3) //currently, invalid
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatched by the client when the player has encountered a physical interaction that would cause damage.<br>
|
* Dispatched by the client when the player has encountered a physical interaction that would cause damage.<br>
|
||||||
|
|
@ -13,7 +30,7 @@ import scodec.codecs._
|
||||||
* The first is the `player`, that is, the client's avatar.
|
* The 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.
|
* 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.
|
* 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 player the player or player-controlled vehicle
|
||||||
* @param target the other party in the collision
|
* @param target the other party in the collision
|
||||||
* @param player_health the player's health
|
* @param player_health the player's health
|
||||||
|
|
@ -22,42 +39,56 @@ import scodec.codecs._
|
||||||
* @param target_velocity the target's velocity
|
* @param target_velocity the target's velocity
|
||||||
* @param player_pos the player's world coordinates
|
* @param player_pos the player's world coordinates
|
||||||
* @param target_pos the target's world coordinates
|
* @param target_pos the target's world coordinates
|
||||||
|
* @param unk1 na
|
||||||
* @param unk2 na
|
* @param unk2 na
|
||||||
* @param unk3 na
|
* @param unk3 na
|
||||||
* @param unk4 na
|
|
||||||
*/
|
*/
|
||||||
final case class GenericCollisionMsg(
|
final case class GenericCollisionMsg(
|
||||||
unk1: Int,
|
collision_type: CollisionIs,
|
||||||
player: PlanetSideGUID,
|
player: PlanetSideGUID,
|
||||||
target: PlanetSideGUID,
|
player_health: Int,
|
||||||
player_health: Int,
|
player_pos: Vector3,
|
||||||
target_health: Int,
|
player_velocity: Vector3,
|
||||||
player_velocity: Vector3,
|
target: PlanetSideGUID,
|
||||||
target_velocity: Vector3,
|
target_health: Int,
|
||||||
player_pos: Vector3,
|
target_pos: Vector3,
|
||||||
target_pos: Vector3,
|
target_velocity: Vector3,
|
||||||
unk2: Long,
|
unk1: Long,
|
||||||
unk3: Long,
|
unk2: Long,
|
||||||
unk4: Long
|
unk3: Long
|
||||||
) extends PlanetSideGamePacket {
|
) extends PlanetSideGamePacket {
|
||||||
type Packet = GenericCollisionMsg
|
type Packet = GenericCollisionMsg
|
||||||
def opcode = GamePacketOpcode.GenericCollisionMsg
|
def opcode = GamePacketOpcode.GenericCollisionMsg
|
||||||
def encode = GenericCollisionMsg.encode(this)
|
def encode = GenericCollisionMsg.encode(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
object GenericCollisionMsg extends Marshallable[GenericCollisionMsg] {
|
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] = (
|
implicit val codec: Codec[GenericCollisionMsg] = (
|
||||||
("unk1" | uint2) ::
|
("collision_type" | collisionIsCodec) ::
|
||||||
("p" | PlanetSideGUID.codec) ::
|
("p" | PlanetSideGUID.codec) ::
|
||||||
("t" | PlanetSideGUID.codec) ::
|
("t" | PlanetSideGUID.codec) ::
|
||||||
("p_health" | uint16L) ::
|
("p_health" | uint16L) ::
|
||||||
("t_health" | uint16L) ::
|
("t_health" | uint16L) ::
|
||||||
("p_vel" | Vector3.codec_float) ::
|
("p_vel" | velocityFloatCodec) ::
|
||||||
("t_vel" | Vector3.codec_float) ::
|
("t_vel" | velocityFloatCodec) ::
|
||||||
("p_pos" | Vector3.codec_pos) ::
|
("p_pos" | Vector3.codec_pos) ::
|
||||||
("t_pos" | Vector3.codec_pos) ::
|
("t_pos" | Vector3.codec_pos) ::
|
||||||
("unk2" | uint32L) ::
|
("unk1" | uint32L) ::
|
||||||
("unk3" | uint32L) ::
|
("unk2" | uint32L) ::
|
||||||
("unk4" | uint32L)
|
("unk3" | uint32L)
|
||||||
).as[GenericCollisionMsg]
|
).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
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 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 vehicle_guid The guid of the vehicle for the requesting player
|
||||||
* @param target_vehicle The cargo vehicle guid e.g. Galaxy / Lodestar
|
* @param target_vehicle The cargo vehicle guid e.g. Galaxy / Lodestar
|
||||||
* @param unk4
|
* @param unk4 na
|
||||||
*/
|
*/
|
||||||
final case class MountVehicleCargoMsg(
|
final case class MountVehicleCargoMsg(
|
||||||
player_guid: PlanetSideGUID,
|
player_guid: PlanetSideGUID,
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,9 @@ import scodec.codecs._
|
||||||
* in repeating order: 13, 14, 10, 11, 12, 15;
|
* in repeating order: 13, 14, 10, 11, 12, 15;
|
||||||
* `None`, when landed and for all vehicles that do not fly
|
* `None`, when landed and for all vehicles that do not fly
|
||||||
* @param unk3 na
|
* @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;
|
* @param wheel_direction for ground vehicles, whether and how much the wheels are being turned;
|
||||||
* 15 for straight;
|
* 15 for straight;
|
||||||
* 0 for hard right;
|
* 0 for hard right;
|
||||||
|
|
@ -80,15 +82,15 @@ object VehicleStateMessage extends Marshallable[VehicleStateMessage] {
|
||||||
|
|
||||||
implicit val codec: Codec[VehicleStateMessage] = (
|
implicit val codec: Codec[VehicleStateMessage] = (
|
||||||
("vehicle_guid" | PlanetSideGUID.codec) ::
|
("vehicle_guid" | PlanetSideGUID.codec) ::
|
||||||
("unk1" | uintL(3)) ::
|
("unk1" | uintL(bits = 3)) ::
|
||||||
("pos" | Vector3.codec_pos) ::
|
("pos" | Vector3.codec_pos) ::
|
||||||
("ang" | codec_orient) ::
|
("ang" | codec_orient) ::
|
||||||
optional(bool, "vel" | Vector3.codec_vel) ::
|
("vel" | optional(bool, Vector3.codec_vel)) ::
|
||||||
optional(bool, "flying" | uintL(5)) ::
|
("flying" | optional(bool, uintL(bits = 5))) ::
|
||||||
("unk3" | uintL(7)) ::
|
("unk3" | uintL(bits = 7)) ::
|
||||||
("unk4" | uint4L) ::
|
("unk4" | uint4L) ::
|
||||||
("wheel_direction" | uintL(5)) ::
|
("wheel_direction" | uintL(5)) ::
|
||||||
("int5" | bool) ::
|
("is_decelerating" | bool) ::
|
||||||
("is_cloaked" | bool)
|
("is_cloaked" | bool)
|
||||||
).as[VehicleStateMessage]
|
).as[VehicleStateMessage]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
package net.psforever.types
|
package net.psforever.types
|
||||||
|
|
||||||
import net.psforever.newcodecs._
|
import net.psforever.newcodecs._
|
||||||
|
import scodec.Attempt.Successful
|
||||||
import scodec.Codec
|
import scodec.Codec
|
||||||
import scodec.codecs._
|
import scodec.codecs._
|
||||||
|
|
||||||
|
|
@ -38,6 +39,18 @@ final case class Vector3(x: Float, y: Float, z: Float) {
|
||||||
Vector3(x * scalar, y * scalar, z * scalar)
|
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
|
* Operator for returning the ground-planar coordinates
|
||||||
* and ignoring the perpendicular distance from the world floor.
|
* and ignoring the perpendicular distance from the world floor.
|
||||||
|
|
@ -100,7 +113,7 @@ object Vector3 {
|
||||||
("x" | newcodecs.q_float(-256.0, 256.0, 14)) ::
|
("x" | newcodecs.q_float(-256.0, 256.0, 14)) ::
|
||||||
("y" | 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))
|
("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] = (
|
implicit val codec_float: Codec[Vector3] = (
|
||||||
("x" | floatL) ::
|
("x" | floatL) ::
|
||||||
|
|
|
||||||
|
|
@ -50,11 +50,11 @@ class CodecTest extends Specification {
|
||||||
val string_vel = hex"857D4E0FFFC0"
|
val string_vel = hex"857D4E0FFFC0"
|
||||||
|
|
||||||
"decode" in {
|
"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 {
|
"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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ class FireHintMessageTest extends Specification {
|
||||||
u2 mustEqual 65399
|
u2 mustEqual 65399
|
||||||
u3 mustEqual 7581
|
u3 mustEqual 7581
|
||||||
u4 mustEqual 0
|
u4 mustEqual 0
|
||||||
u5 mustEqual None
|
u5.isEmpty mustEqual true
|
||||||
case _ =>
|
case _ =>
|
||||||
ko
|
ko
|
||||||
}
|
}
|
||||||
|
|
@ -34,7 +34,7 @@ class FireHintMessageTest extends Specification {
|
||||||
u2 mustEqual 65231
|
u2 mustEqual 65231
|
||||||
u3 mustEqual 64736
|
u3 mustEqual 64736
|
||||||
u4 mustEqual 3
|
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 _ =>
|
case _ =>
|
||||||
ko
|
ko
|
||||||
}
|
}
|
||||||
|
|
@ -46,7 +46,7 @@ class FireHintMessageTest extends Specification {
|
||||||
|
|
||||||
pkt mustEqual string
|
pkt mustEqual string
|
||||||
}
|
}
|
||||||
"encode string2" in {
|
"encode (string2)" in {
|
||||||
val msg = FireHintMessage(
|
val msg = FireHintMessage(
|
||||||
PlanetSideGUID(3592),
|
PlanetSideGUID(3592),
|
||||||
Vector3(2910.789f, 3744.875f, 69.0625f),
|
Vector3(2910.789f, 3744.875f, 69.0625f),
|
||||||
|
|
@ -54,7 +54,7 @@ class FireHintMessageTest extends Specification {
|
||||||
65231,
|
65231,
|
||||||
64736,
|
64736,
|
||||||
3,
|
3,
|
||||||
Some(Vector3(21.5f, -6.8125f, 2.65625f))
|
Some(Vector3(77.4f, -24.525f, 9.5625f))
|
||||||
)
|
)
|
||||||
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,17 +11,18 @@ class GenericCollisionMsgTest extends Specification {
|
||||||
//TODO find a better test later
|
//TODO find a better test later
|
||||||
val string =
|
val string =
|
||||||
hex"3C 92C00000190000001B2A8010932CEF505C70946F00000000000000000000000017725EBC6D6A058000000000000000000000000000003F8FF45140"
|
hex"3C 92C00000190000001B2A8010932CEF505C70946F00000000000000000000000017725EBC6D6A058000000000000000000000000000003F8FF45140"
|
||||||
|
|
||||||
"decode" in {
|
"decode" in {
|
||||||
PacketCoding.decodePacket(string).require match {
|
PacketCoding.decodePacket(string).require match {
|
||||||
case GenericCollisionMsg(unk1, p, t, php, thp, pv, tv, ppos, tpos, unk2, unk3, unk4) =>
|
case GenericCollisionMsg(ct, p, php, ppos, pv, t, thp, tpos, tv, unk2, unk3, unk4) =>
|
||||||
unk1 mustEqual 2
|
ct mustEqual CollisionIs.OfInfantry
|
||||||
p mustEqual PlanetSideGUID(75)
|
p mustEqual PlanetSideGUID(75)
|
||||||
t mustEqual PlanetSideGUID(0)
|
t mustEqual PlanetSideGUID(0)
|
||||||
php mustEqual 100
|
php mustEqual 100
|
||||||
thp mustEqual 0
|
thp mustEqual 0
|
||||||
pv.x mustEqual 32.166428f
|
pv.x mustEqual 115.79913f
|
||||||
pv.y mustEqual 23.712547f
|
pv.y mustEqual 85.365166f
|
||||||
pv.z mustEqual -0.012802706f
|
pv.z mustEqual -0.046089742f
|
||||||
tv.x mustEqual 0.0f
|
tv.x mustEqual 0.0f
|
||||||
tv.z mustEqual 0.0f
|
tv.z mustEqual 0.0f
|
||||||
tv.x mustEqual 0.0f
|
tv.x mustEqual 0.0f
|
||||||
|
|
@ -38,22 +39,48 @@ class GenericCollisionMsgTest extends Specification {
|
||||||
ko
|
ko
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"encode" in {
|
"encode" in {
|
||||||
val msg = GenericCollisionMsg(
|
val msg = GenericCollisionMsg(
|
||||||
2,
|
CollisionIs.OfInfantry,
|
||||||
PlanetSideGUID(75),
|
PlanetSideGUID(75),
|
||||||
PlanetSideGUID(0),
|
|
||||||
100,
|
100,
|
||||||
0,
|
|
||||||
Vector3(32.166428f, 23.712547f, -0.012802706f),
|
|
||||||
Vector3(0.0f, 0.0f, 0.0f),
|
|
||||||
Vector3(3986.7266f, 2615.3672f, 90.625f),
|
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),
|
Vector3(0.0f, 0.0f, 0.0f),
|
||||||
0L,
|
0L,
|
||||||
0L,
|
0L,
|
||||||
1171341310L
|
1171341310L
|
||||||
)
|
)
|
||||||
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ class LongRangeProjectileInfoMessageTest extends Specification {
|
||||||
case LongRangeProjectileInfoMessage(guid, pos, vel) =>
|
case LongRangeProjectileInfoMessage(guid, pos, vel) =>
|
||||||
guid mustEqual PlanetSideGUID(5330)
|
guid mustEqual PlanetSideGUID(5330)
|
||||||
pos mustEqual Vector3(2264, 5115.039f, 31.046875f)
|
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 _ =>
|
case _ =>
|
||||||
ko
|
ko
|
||||||
}
|
}
|
||||||
|
|
@ -25,7 +25,7 @@ class LongRangeProjectileInfoMessageTest extends Specification {
|
||||||
val msg = LongRangeProjectileInfoMessage(
|
val msg = LongRangeProjectileInfoMessage(
|
||||||
PlanetSideGUID(5330),
|
PlanetSideGUID(5330),
|
||||||
Vector3(2264, 5115.039f, 31.046875f),
|
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
|
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||||
pkt mustEqual string
|
pkt mustEqual string
|
||||||
|
|
|
||||||
|
|
@ -99,8 +99,8 @@ class PlayerStateMessageTest extends Specification {
|
||||||
pos.y mustEqual 5987.6016f
|
pos.y mustEqual 5987.6016f
|
||||||
pos.z mustEqual 44.1875f
|
pos.z mustEqual 44.1875f
|
||||||
vel.isDefined mustEqual true
|
vel.isDefined mustEqual true
|
||||||
vel.get.x mustEqual 2.53125f
|
vel.get.x mustEqual 9.1125f
|
||||||
vel.get.y mustEqual 6.5625f
|
vel.get.y mustEqual 23.625f
|
||||||
vel.get.z mustEqual 0.0f
|
vel.get.z mustEqual 0.0f
|
||||||
facingYaw mustEqual 22.5f
|
facingYaw mustEqual 22.5f
|
||||||
facingPitch mustEqual -11.25f
|
facingPitch mustEqual -11.25f
|
||||||
|
|
@ -155,7 +155,7 @@ class PlayerStateMessageTest extends Specification {
|
||||||
val msg = PlayerStateMessage(
|
val msg = PlayerStateMessage(
|
||||||
PlanetSideGUID(1696),
|
PlanetSideGUID(1696),
|
||||||
Vector3(4008.6016f, 5987.6016f, 44.1875f),
|
Vector3(4008.6016f, 5987.6016f, 44.1875f),
|
||||||
Some(Vector3(2.53125f, 6.5625f, 0f)),
|
Some(Vector3(9.1125f, 23.625f, 0f)),
|
||||||
22.5f,
|
22.5f,
|
||||||
-11.25f,
|
-11.25f,
|
||||||
0f,
|
0f,
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ class PlayerStateMessageUpstreamTest extends Specification {
|
||||||
) =>
|
) =>
|
||||||
avatar_guid mustEqual PlanetSideGUID(75)
|
avatar_guid mustEqual PlanetSideGUID(75)
|
||||||
pos mustEqual Vector3(3694.1094f, 2735.4531f, 90.84375f)
|
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
|
facingYaw mustEqual 61.875f
|
||||||
facingPitch mustEqual -8.4375f
|
facingPitch mustEqual -8.4375f
|
||||||
facingYawUpper mustEqual 0.0f
|
facingYawUpper mustEqual 0.0f
|
||||||
|
|
@ -51,7 +51,7 @@ class PlayerStateMessageUpstreamTest extends Specification {
|
||||||
val msg = PlayerStateMessageUpstream(
|
val msg = PlayerStateMessageUpstream(
|
||||||
PlanetSideGUID(75),
|
PlanetSideGUID(75),
|
||||||
Vector3(3694.1094f, 2735.4531f, 90.84375f),
|
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,
|
61.875f,
|
||||||
-8.4375f,
|
-8.4375f,
|
||||||
0.0f,
|
0.0f,
|
||||||
|
|
|
||||||
|
|
@ -49,9 +49,9 @@ class PlayerStateShiftMessageTest extends Specification {
|
||||||
state.get.pos.z mustEqual 50.3125f
|
state.get.pos.z mustEqual 50.3125f
|
||||||
state.get.viewYawLim mustEqual 50.625f
|
state.get.viewYawLim mustEqual 50.625f
|
||||||
state.get.vel.isDefined mustEqual true
|
state.get.vel.isDefined mustEqual true
|
||||||
state.get.vel.get.x mustEqual 2.8125f
|
state.get.vel.get.x mustEqual 10.125f
|
||||||
state.get.vel.get.y mustEqual -8.0f
|
state.get.vel.get.y mustEqual -28.8f
|
||||||
state.get.vel.get.z mustEqual 0.375f
|
state.get.vel.get.z mustEqual 1.3499999f
|
||||||
unk.isDefined mustEqual false
|
unk.isDefined mustEqual false
|
||||||
case _ =>
|
case _ =>
|
||||||
ko
|
ko
|
||||||
|
|
@ -74,7 +74,7 @@ class PlayerStateShiftMessageTest extends Specification {
|
||||||
|
|
||||||
"encode (pos and vel)" in {
|
"encode (pos and vel)" in {
|
||||||
val msg = PlayerStateShiftMessage(
|
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
|
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,7 @@ class SplashHitMessageTest extends Specification {
|
||||||
projectile_pos.z mustEqual 90.921875f
|
projectile_pos.z mustEqual 90.921875f
|
||||||
unk2 mustEqual 0
|
unk2 mustEqual 0
|
||||||
unk3 mustEqual 0
|
unk3 mustEqual 0
|
||||||
projectile_vel.isDefined mustEqual true
|
projectile_vel.contains(Vector3(7.9874997f, 3.2624998f, -4.0499997f)) mustEqual true
|
||||||
projectile_vel.get.x mustEqual 2.21875f
|
|
||||||
projectile_vel.get.y mustEqual 0.90625f
|
|
||||||
projectile_vel.get.z mustEqual -1.125f
|
|
||||||
unk4.isDefined mustEqual false
|
unk4.isDefined mustEqual false
|
||||||
targets.size mustEqual 2
|
targets.size mustEqual 2
|
||||||
//0
|
//0
|
||||||
|
|
@ -53,7 +50,7 @@ class SplashHitMessageTest extends Specification {
|
||||||
Vector3(3681.3438f, 2728.9062f, 90.921875f),
|
Vector3(3681.3438f, 2728.9062f, 90.921875f),
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
Some(Vector3(2.21875f, 0.90625f, -1.125f)),
|
Some(Vector3(7.9874997f, 3.2624998f, -4.0499997f)),
|
||||||
None,
|
None,
|
||||||
SplashedTarget(PlanetSideGUID(75), Vector3(3674.8438f, 2726.789f, 91.15625f), 286326784L, 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) ::
|
SplashedTarget(PlanetSideGUID(372), Vector3(3679.1328f, 2722.6016f, 92.765625f), 268435456L, None) ::
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ class VehicleStateMessageTest extends Specification {
|
||||||
vel.isDefined mustEqual true
|
vel.isDefined mustEqual true
|
||||||
vel.get.x mustEqual 0.0f
|
vel.get.x mustEqual 0.0f
|
||||||
vel.get.y 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
|
unk2.isDefined mustEqual false
|
||||||
unk3 mustEqual 0
|
unk3 mustEqual 0
|
||||||
unk4 mustEqual 0
|
unk4 mustEqual 0
|
||||||
|
|
@ -42,7 +42,7 @@ class VehicleStateMessageTest extends Specification {
|
||||||
0,
|
0,
|
||||||
Vector3(3674.8438f, 2726.789f, 91.09375f),
|
Vector3(3674.8438f, 2726.789f, 91.09375f),
|
||||||
Vector3(359.29688f, 1.0546875f, 90.35156f),
|
Vector3(359.29688f, 1.0546875f, 90.35156f),
|
||||||
Some(Vector3(0.0f, 0.0f, 0.03125f)),
|
Some(Vector3(0.0f, 0.0f, 0.1125f)),
|
||||||
None,
|
None,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ class VehicleSubStateMessageTest extends Specification {
|
||||||
vehicle_pos mustEqual Vector3(3465.9575f, 2873.3635f, 91.05253f)
|
vehicle_pos mustEqual Vector3(3465.9575f, 2873.3635f, 91.05253f)
|
||||||
vehicle_ang mustEqual Vector3(11.6015625f, 0.0f, 3.515625f)
|
vehicle_ang mustEqual Vector3(11.6015625f, 0.0f, 3.515625f)
|
||||||
vel.isDefined mustEqual true
|
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
|
unk1 mustEqual false
|
||||||
unk2.isDefined mustEqual false
|
unk2.isDefined mustEqual false
|
||||||
case _ =>
|
case _ =>
|
||||||
|
|
@ -32,7 +32,7 @@ class VehicleSubStateMessageTest extends Specification {
|
||||||
PlanetSideGUID(3376),
|
PlanetSideGUID(3376),
|
||||||
Vector3(3465.9575f, 2873.3635f, 91.05253f),
|
Vector3(3465.9575f, 2873.3635f, 91.05253f),
|
||||||
Vector3(11.6015625f, 0.0f, 3.515625f),
|
Vector3(11.6015625f, 0.0f, 3.515625f),
|
||||||
Some(Vector3(-0.40625f, 0.03125f, -0.8125f)),
|
Some(Vector3(-1.4625f, 0.1125f, -2.925f)),
|
||||||
false,
|
false,
|
||||||
None
|
None
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ class CharacterDataTest extends Specification {
|
||||||
pos.coord mustEqual Vector3(3674.8438f, 2726.789f, 91.15625f)
|
pos.coord mustEqual Vector3(3674.8438f, 2726.789f, 91.15625f)
|
||||||
pos.orient mustEqual Vector3(0f, 0f, 64.6875f)
|
pos.orient mustEqual Vector3(0f, 0f, 64.6875f)
|
||||||
pos.vel.isDefined mustEqual true
|
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 {
|
basic match {
|
||||||
case CharacterAppearanceData(a, b, ribbons) =>
|
case CharacterAppearanceData(a, b, ribbons) =>
|
||||||
|
|
@ -294,7 +294,7 @@ class CharacterDataTest extends Specification {
|
||||||
val pos: PlacementData = PlacementData(
|
val pos: PlacementData = PlacementData(
|
||||||
Vector3(3674.8438f, 2726.789f, 91.15625f),
|
Vector3(3674.8438f, 2726.789f, 91.15625f),
|
||||||
Vector3(0f, 0f, 64.6875f),
|
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(
|
val a: Int => CharacterAppearanceA = CharacterAppearanceA(
|
||||||
BasicCharacterData(
|
BasicCharacterData(
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ class MountedVehiclesTest extends Specification {
|
||||||
case vdata: VehicleData =>
|
case vdata: VehicleData =>
|
||||||
vdata.pos.coord mustEqual Vector3(4571.6875f, 5602.1875f, 93)
|
vdata.pos.coord mustEqual Vector3(4571.6875f, 5602.1875f, 93)
|
||||||
vdata.pos.orient mustEqual Vector3(11.25f, 2.8125f, 92.8125f)
|
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.faction mustEqual PlanetSideEmpire.TR
|
||||||
vdata.data.bops mustEqual false
|
vdata.data.bops mustEqual false
|
||||||
vdata.data.alternate mustEqual false
|
vdata.data.alternate mustEqual false
|
||||||
|
|
@ -264,7 +264,7 @@ class MountedVehiclesTest extends Specification {
|
||||||
PlacementData(
|
PlacementData(
|
||||||
Vector3(4571.6875f, 5602.1875f, 93),
|
Vector3(4571.6875f, 5602.1875f, 93),
|
||||||
Vector3(11.25f, 2.8125f, 92.8125f),
|
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)),
|
CommonFieldData(PlanetSideEmpire.TR, false, false, false, None, false, Some(false), None, PlanetSideGUID(3776)),
|
||||||
false,
|
false,
|
||||||
|
|
|
||||||
|
|
@ -1061,7 +1061,7 @@ class DamageableWeaponTurretDestructionTest extends ActorTest {
|
||||||
assert(!turret.Destroyed)
|
assert(!turret.Destroyed)
|
||||||
|
|
||||||
turret.Actor ! Vitality.Damage(applyDamageToA) //also test destruction while jammered
|
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.Health > turret.Definition.DamageDestroysAt)
|
||||||
assert(turret.Jammed)
|
assert(turret.Jammed)
|
||||||
assert(!turret.Destroyed)
|
assert(!turret.Destroyed)
|
||||||
|
|
@ -1071,44 +1071,37 @@ class DamageableWeaponTurretDestructionTest extends ActorTest {
|
||||||
player1Probe.expectNoMessage(500 milliseconds)
|
player1Probe.expectNoMessage(500 milliseconds)
|
||||||
val msg3 = player2Probe.receiveOne(200 milliseconds)
|
val msg3 = player2Probe.receiveOne(200 milliseconds)
|
||||||
val msg56 = vehicleProbe.receiveN(2, 200 milliseconds)
|
val msg56 = vehicleProbe.receiveN(2, 200 milliseconds)
|
||||||
assert(
|
msg12_4.head match {
|
||||||
msg12_4.head match {
|
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => ;
|
||||||
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
|
case _ =>
|
||||||
case _ => false
|
assert(false, s"DamageableWeaponTurretDestructionTest-1: ${msg12_4.head}")
|
||||||
}
|
}
|
||||||
)
|
msg12_4(1) match {
|
||||||
assert(
|
case AvatarServiceMessage("test", AvatarAction.Destroy(PlanetSideGUID(2), _, _, Vector3(1, 0, 0))) => ;
|
||||||
msg12_4(1) match {
|
case _ =>
|
||||||
case AvatarServiceMessage("test", AvatarAction.Destroy(PlanetSideGUID(2), _, _, Vector3(1, 0, 0))) => true
|
assert(false, s"DamageableWeaponTurretDestructionTest-2: ${msg12_4(1)}")
|
||||||
case _ => false
|
}
|
||||||
}
|
msg3 match {
|
||||||
)
|
case Player.Die(_) => true
|
||||||
assert(
|
case _ =>
|
||||||
msg3 match {
|
assert(false, s"DamageableWeaponTurretDestructionTest-3: player not dead - $msg3")
|
||||||
case Player.Die(_) => true
|
}
|
||||||
case _ => false
|
msg12_4(2) match {
|
||||||
}
|
case AvatarServiceMessage("test", AvatarAction.ObjectDelete(PlanetSideGUID(0), PlanetSideGUID(5), _)) => ;
|
||||||
)
|
case _ =>
|
||||||
assert(
|
assert(false, s"DamageableWeaponTurretDestructionTest-4: ${msg12_4(2)}")
|
||||||
msg12_4(2) match {
|
}
|
||||||
case AvatarServiceMessage("test", AvatarAction.ObjectDelete(PlanetSideGUID(0), PlanetSideGUID(5), _)) => true
|
msg56.head match {
|
||||||
case _ => false
|
case VehicleServiceMessage.TurretUpgrade(SupportActor.ClearSpecific(List(t), _)) if turret eq t => ;
|
||||||
}
|
case _ =>
|
||||||
)
|
assert(false, s"DamageableWeaponTurretDestructionTest-5: ${msg56.head}")
|
||||||
assert(
|
}
|
||||||
msg56.head match {
|
msg56(1) match {
|
||||||
case VehicleServiceMessage.TurretUpgrade(SupportActor.ClearSpecific(List(t), _)) if turret eq t => true
|
case VehicleServiceMessage.TurretUpgrade(TurretUpgrader.AddTask(t, _, TurretUpgrade.None, _)) if t eq turret => ;
|
||||||
case _ => false
|
true
|
||||||
}
|
case _ =>
|
||||||
)
|
assert(false, s"DamageableWeaponTurretDestructionTest-6: ${msg56(1)}")
|
||||||
assert(
|
}
|
||||||
msg56(1) match {
|
|
||||||
case VehicleServiceMessage.TurretUpgrade(TurretUpgrader.AddTask(t, _, TurretUpgrade.None, _))
|
|
||||||
if t eq turret =>
|
|
||||||
true
|
|
||||||
case _ => false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
assert(turret.Health <= turret.Definition.DamageDestroysAt)
|
assert(turret.Health <= turret.Definition.DamageDestroysAt)
|
||||||
assert(!turret.Jammed)
|
assert(!turret.Jammed)
|
||||||
assert(turret.Destroyed)
|
assert(turret.Destroyed)
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import net.psforever.objects.guid.source.MaxNumberSource
|
||||||
import net.psforever.objects.serverobject.environment._
|
import net.psforever.objects.serverobject.environment._
|
||||||
import net.psforever.objects.serverobject.mount.Mountable
|
import net.psforever.objects.serverobject.mount.Mountable
|
||||||
import net.psforever.objects.vehicles.VehicleLockState
|
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.vital.{VehicleShieldCharge, Vitality}
|
||||||
import net.psforever.objects.zones.{Zone, ZoneMap}
|
import net.psforever.objects.zones.{Zone, ZoneMap}
|
||||||
import net.psforever.packet.game._
|
import net.psforever.packet.game._
|
||||||
|
|
@ -66,16 +66,11 @@ class VehicleControlPrepareForDeletionPassengerTest extends ActorTest {
|
||||||
vehicle.Actor ! Vehicle.Deconstruct()
|
vehicle.Actor ! Vehicle.Deconstruct()
|
||||||
|
|
||||||
val vehicle_msg = vehicleProbe.receiveN(1, 500 milliseconds)
|
val vehicle_msg = vehicleProbe.receiveN(1, 500 milliseconds)
|
||||||
assert(
|
vehicle_msg.head match {
|
||||||
vehicle_msg.head match {
|
case VehicleServiceMessage("test", VehicleAction.KickPassenger(PlanetSideGUID(2), 4, true, PlanetSideGUID(1))) => ;
|
||||||
case VehicleServiceMessage(
|
case _ =>
|
||||||
"test",
|
assert(false, s"VehicleControlPrepareForDeletionPassengerTest: ${vehicle_msg.head}")
|
||||||
VehicleAction.KickPassenger(PlanetSideGUID(2), 4, false, PlanetSideGUID(1))
|
}
|
||||||
) =>
|
|
||||||
true
|
|
||||||
case _ => false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
assert(player1.VehicleSeated.isEmpty)
|
assert(player1.VehicleSeated.isEmpty)
|
||||||
assert(vehicle.Seats(1).occupant.isEmpty)
|
assert(vehicle.Seats(1).occupant.isEmpty)
|
||||||
}
|
}
|
||||||
|
|
@ -96,19 +91,22 @@ class VehicleControlPrepareForDeletionMountedInTest extends FreedContextActorTes
|
||||||
|
|
||||||
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
|
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
|
||||||
vehicle.Faction = PlanetSideEmpire.TR
|
vehicle.Faction = PlanetSideEmpire.TR
|
||||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test-cargo")
|
|
||||||
vehicle.Zone = zone
|
vehicle.Zone = zone
|
||||||
val lodestar = Vehicle(GlobalDefinitions.lodestar)
|
val lodestar = Vehicle(GlobalDefinitions.lodestar)
|
||||||
lodestar.Faction = PlanetSideEmpire.TR
|
lodestar.Faction = PlanetSideEmpire.TR
|
||||||
|
lodestar.Zone = zone
|
||||||
val player1 = Player(VehicleTest.avatar1) //name="test1"
|
val player1 = Player(VehicleTest.avatar1) //name="test1"
|
||||||
val player2 = Player(VehicleTest.avatar2) //name="test2"
|
val player2 = Player(VehicleTest.avatar2) //name="test2"
|
||||||
|
|
||||||
guid.register(vehicle, 1)
|
guid.register(vehicle, 1)
|
||||||
guid.register(lodestar, 2)
|
guid.register(lodestar, 2)
|
||||||
player1.GUID = PlanetSideGUID(3)
|
guid.register(player1, 3)
|
||||||
var utilityId = 10
|
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 =>
|
lodestar.Utilities.values.foreach { util =>
|
||||||
util().GUID = PlanetSideGUID(utilityId)
|
guid.register(util(), utilityId)
|
||||||
utilityId += 1
|
utilityId += 1
|
||||||
}
|
}
|
||||||
vehicle.Seats(1).mount(player1) //passenger mount
|
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
|
zone.Transport ! Zone.Vehicle.Spawn(lodestar) //can not fake this
|
||||||
|
|
||||||
"VehicleControl" should {
|
"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()
|
vehicle.Actor ! Vehicle.Deconstruct()
|
||||||
|
|
||||||
val vehicle_msg = vehicleProbe.receiveN(6, 500 milliseconds)
|
val vehicle_msg = vehicleProbe.receiveN(6, 1 minute)
|
||||||
//dismounting as cargo messages
|
//dismounting as cargo messages
|
||||||
assert(
|
vehicle_msg.head match {
|
||||||
vehicle_msg.head match {
|
case VehicleServiceMessage("test", VehicleAction.KickPassenger(PlanetSideGUID(3), 4, true, PlanetSideGUID(1))) => ;
|
||||||
case VehicleServiceMessage(
|
case _ =>
|
||||||
_,
|
assert(false, s"VehicleControlPrepareForDeletionMountedInTest-1: ${vehicle_msg.head}")
|
||||||
VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 0, _))
|
}
|
||||||
) =>
|
vehicle_msg(1) match {
|
||||||
true
|
case VehicleServiceMessage(_, VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 0, _))) => ;
|
||||||
case _ => false
|
case _ =>
|
||||||
}
|
assert(false, s"VehicleControlPrepareForDeletionMountedInTest-2: ${vehicle_msg(1)}")
|
||||||
)
|
}
|
||||||
assert(
|
vehicle_msg(2) match {
|
||||||
vehicle_msg(1) match {
|
case VehicleServiceMessage(_, VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 68, _))) => ;
|
||||||
case VehicleServiceMessage(
|
case _ =>
|
||||||
_,
|
assert(false, s"VehicleControlPrepareForDeletionMountedInTest-3: ${vehicle_msg(2)}")
|
||||||
VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 68, _))
|
}
|
||||||
) =>
|
vehicle_msg(3) match {
|
||||||
true
|
case VehicleServiceMessage("test", VehicleAction.SendResponse(_, CargoMountPointStatusMessage(PlanetSideGUID(2), _, PlanetSideGUID(1), _, 1, CargoStatus.InProgress, 0))) => ;
|
||||||
case _ => false
|
case _ =>
|
||||||
}
|
assert(false, s"VehicleControlPrepareForDeletionMountedInTest-4: ${vehicle_msg(3)}")
|
||||||
)
|
}
|
||||||
assert(
|
vehicle_msg(4) match {
|
||||||
vehicle_msg(2) match {
|
case VehicleServiceMessage("test", VehicleAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _, _, _))) => ;
|
||||||
case VehicleServiceMessage(
|
case _ =>
|
||||||
"test",
|
assert(false, s"VehicleControlPrepareForDeletionMountedInTest-5: ${vehicle_msg(4)}")
|
||||||
VehicleAction.SendResponse(
|
}
|
||||||
_,
|
vehicle_msg(5) match {
|
||||||
CargoMountPointStatusMessage(PlanetSideGUID(2), _, PlanetSideGUID(1), _, 1, CargoStatus.InProgress, 0)
|
case VehicleServiceMessage("test", VehicleAction.SendResponse(_, CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0))) => ;
|
||||||
)
|
case _ =>
|
||||||
) =>
|
assert(false, s"VehicleControlPrepareForDeletionMountedInTest-6: ${vehicle_msg(5)}")
|
||||||
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
|
|
||||||
}
|
|
||||||
)
|
|
||||||
assert(player1.VehicleSeated.isEmpty)
|
assert(player1.VehicleSeated.isEmpty)
|
||||||
assert(vehicle.Seats(1).occupant.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)
|
val vehicle_msg = vehicleProbe.receiveN(6, 500 milliseconds)
|
||||||
vehicle_msg(5) match {
|
vehicle_msg(5) match {
|
||||||
case VehicleServiceMessage(
|
case VehicleServiceMessage("test", VehicleAction.KickPassenger(PlanetSideGUID(4), 4, true, PlanetSideGUID(2))) => ;
|
||||||
"test",
|
case _ =>
|
||||||
VehicleAction.KickPassenger(PlanetSideGUID(4), 4, false, PlanetSideGUID(2))
|
assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-1: ${vehicle_msg(5)}")
|
||||||
) => ;
|
|
||||||
case _ => assert(false)
|
|
||||||
}
|
}
|
||||||
assert(player2.VehicleSeated.isEmpty)
|
assert(player2.VehicleSeated.isEmpty)
|
||||||
assert(lodestar.Seats(0).occupant.isEmpty)
|
assert(lodestar.Seats(0).occupant.isEmpty)
|
||||||
//cargo dismounting messages
|
//cargo dismounting messages
|
||||||
vehicle_msg.head match {
|
vehicle_msg.head match {
|
||||||
case VehicleServiceMessage(
|
case VehicleServiceMessage(_, VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 0, _))) => ;
|
||||||
_,
|
case _ =>
|
||||||
VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 0, _))
|
assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-2: ${vehicle_msg.head}")
|
||||||
) => ;
|
|
||||||
case _ => assert(false)
|
|
||||||
}
|
}
|
||||||
vehicle_msg(1) match {
|
vehicle_msg(1) match {
|
||||||
case VehicleServiceMessage(
|
case VehicleServiceMessage(_, VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 68, _))) => ;
|
||||||
_,
|
case _ =>
|
||||||
VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 68, _))
|
assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-3: ${vehicle_msg(1)}")
|
||||||
) => ;
|
|
||||||
case _ => assert(false)
|
|
||||||
}
|
}
|
||||||
vehicle_msg(2) match {
|
vehicle_msg(2) match {
|
||||||
case VehicleServiceMessage(
|
case VehicleServiceMessage("test", VehicleAction.SendResponse(_, CargoMountPointStatusMessage(PlanetSideGUID(2), _, PlanetSideGUID(1), _, 1, CargoStatus.InProgress, 0))) => ;
|
||||||
"test",
|
case _ =>
|
||||||
VehicleAction.SendResponse(
|
assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-4: ${vehicle_msg(2)}")
|
||||||
_,
|
|
||||||
CargoMountPointStatusMessage(PlanetSideGUID(2), _, PlanetSideGUID(1), _, 1, CargoStatus.InProgress, 0)
|
|
||||||
)
|
|
||||||
) => ;
|
|
||||||
case _ => assert(false)
|
|
||||||
}
|
}
|
||||||
vehicle_msg(3) match {
|
vehicle_msg(3) match {
|
||||||
case VehicleServiceMessage(
|
case VehicleServiceMessage("test", VehicleAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _, _, _))) => ;
|
||||||
"test",
|
case _ =>
|
||||||
VehicleAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _, _, _))
|
assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-5: ${vehicle_msg(3)}")
|
||||||
) => ;
|
|
||||||
case _ => assert(false)
|
|
||||||
}
|
}
|
||||||
vehicle_msg(4) match {
|
vehicle_msg(4) match {
|
||||||
case VehicleServiceMessage(
|
case VehicleServiceMessage("test", VehicleAction.SendResponse(_, CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0))) => ;
|
||||||
"test",
|
case _ =>
|
||||||
VehicleAction.SendResponse(
|
assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-6: ${vehicle_msg(4)}")
|
||||||
_,
|
|
||||||
CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0)
|
|
||||||
)
|
|
||||||
) => ;
|
|
||||||
case _ => assert(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue