mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-01-19 18:44:45 +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
|
|
@ -44,6 +44,7 @@ import net.psforever.objects.vehicles.Utility.InternalTelepad
|
|||
import net.psforever.objects.vehicles._
|
||||
import net.psforever.objects.vital._
|
||||
import net.psforever.objects.vital.base._
|
||||
import net.psforever.objects.vital.collision.{CollisionReason, CollisionWithReason}
|
||||
import net.psforever.objects.vital.etc.ExplodingEntityReason
|
||||
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||
import net.psforever.objects.vital.projectile.ProjectileReason
|
||||
|
|
@ -206,7 +207,8 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
var nextSpawnPoint: Option[SpawnPoint] = None
|
||||
var setupAvatarFunc: () => Unit = AvatarCreate
|
||||
var setCurrentAvatarFunc: Player => Unit = SetCurrentAvatarNormally
|
||||
var persist: () => Unit = NoPersistence
|
||||
var persistFunc: () => Unit = NoPersistence
|
||||
var persist: () => Unit = UpdatePersistenceOnly
|
||||
var specialItemSlotGuid: Option[PlanetSideGUID] =
|
||||
None // If a special item (e.g. LLU) has been attached to the player the GUID should be stored here, or cleared when dropped, since the drop hotkey doesn't send the GUID of the object to be dropped.
|
||||
|
||||
|
|
@ -275,6 +277,10 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
var setAvatar: Boolean = false
|
||||
var turnCounterFunc: PlanetSideGUID => Unit = TurnCounterDuringInterim
|
||||
var waypointCooldown: Long = 0L
|
||||
var heightLast: Float = 0f
|
||||
var heightTrend: Boolean = false //up = true, down = false
|
||||
var heightHistory: Float = 0f
|
||||
val collisionHistory: mutable.HashMap[ActorRef, Long] = mutable.HashMap()
|
||||
|
||||
var clientKeepAlive: Cancellable = Default.Cancellable
|
||||
var progressBarUpdate: Cancellable = Default.Cancellable
|
||||
|
|
@ -1172,6 +1178,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
case NewPlayerLoaded(tplayer) =>
|
||||
//new zone
|
||||
log.info(s"${tplayer.Name} has spawned into ${session.zone.id}")
|
||||
persist = UpdatePersistenceAndRefs
|
||||
tplayer.avatar = avatar
|
||||
session = session.copy(player = tplayer)
|
||||
avatarActor ! AvatarActor.CreateImplants()
|
||||
|
|
@ -1379,7 +1386,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
case PlayerToken.LoginInfo(name, Zone.Nowhere, _) =>
|
||||
log.info(s"LoginInfo: player $name is considered a new character")
|
||||
//TODO poll the database for saved zone and coordinates?
|
||||
persist = UpdatePersistence(sender())
|
||||
persistFunc = UpdatePersistence(sender())
|
||||
deadState = DeadState.RespawnTime
|
||||
|
||||
session = session.copy(player = new Player(avatar))
|
||||
|
|
@ -1395,7 +1402,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
|
||||
case PlayerToken.LoginInfo(playerName, inZone, pos) =>
|
||||
log.info(s"LoginInfo: player $playerName is already logged in zone ${inZone.id}; rejoining that character")
|
||||
persist = UpdatePersistence(sender())
|
||||
persistFunc = UpdatePersistence(sender())
|
||||
//tell the old WorldSessionActor to kill itself by using its own subscriptions against itself
|
||||
inZone.AvatarEvents ! AvatarServiceMessage(playerName, AvatarAction.TeardownConnection())
|
||||
//find and reload previous player
|
||||
|
|
@ -1477,18 +1484,36 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
|
||||
/**
|
||||
* Update this player avatar for persistence.
|
||||
* @param persistRef reference to the persistence monitor
|
||||
* Set to `persist` initially.
|
||||
*/
|
||||
def UpdatePersistence(persistRef: ActorRef)(): Unit = {
|
||||
persistRef ! AccountPersistenceService.Update(player.Name, continent, player.Position)
|
||||
def UpdatePersistenceOnly(): Unit = {
|
||||
persistFunc()
|
||||
}
|
||||
|
||||
/**
|
||||
* Update this player avatar for persistence.
|
||||
* Set to `persist` when (new) player is loaded.
|
||||
*/
|
||||
def UpdatePersistenceAndRefs(): Unit = {
|
||||
persistFunc()
|
||||
updateOldRefsMap()
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not update this player avatar for persistence.
|
||||
* Set to `persistFunc` initially.
|
||||
*/
|
||||
def NoPersistence(): Unit = {}
|
||||
|
||||
/**
|
||||
* Update this player avatar for persistence.
|
||||
* Set this to `persistFunc` when persistence is ready.
|
||||
* @param persistRef reference to the persistence monitor
|
||||
*/
|
||||
def UpdatePersistence(persistRef: ActorRef)(): Unit = {
|
||||
persistRef ! AccountPersistenceService.Update(player.Name, continent, player.Position)
|
||||
}
|
||||
|
||||
/**
|
||||
* A zoning message was received.
|
||||
* That doesn't matter.
|
||||
|
|
@ -1585,11 +1610,11 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
CancelAllProximityUnits()
|
||||
//droppod action
|
||||
val droppod = Vehicle(GlobalDefinitions.droppod)
|
||||
droppod.GUID = PlanetSideGUID(0) //droppod is not registered, we must jury-rig this
|
||||
droppod.Faction = player.Faction
|
||||
droppod.Position = spawnPosition.xy + Vector3.z(1024)
|
||||
droppod.Orientation = Vector3.z(180) //you always seems to land looking south; don't know why
|
||||
droppod.Seats(0).mount(player)
|
||||
droppod.GUID = PlanetSideGUID(0) //droppod is not registered, we must jury-rig this
|
||||
droppod.Invalidate() //now, we must short-circuit the jury-rig
|
||||
interstellarFerry = Some(droppod) //leverage vehicle gating
|
||||
player.Position = droppod.Position
|
||||
|
|
@ -1642,7 +1667,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
*/
|
||||
def HandleAvatarServiceResponse(toChannel: String, guid: PlanetSideGUID, reply: AvatarResponse.Response): Unit = {
|
||||
val tplayer_guid =
|
||||
if (player.HasGUID) player.GUID
|
||||
if (player != null && player.HasGUID) player.GUID
|
||||
else PlanetSideGUID(0)
|
||||
reply match {
|
||||
case AvatarResponse.TeardownConnection() =>
|
||||
|
|
@ -2525,12 +2550,12 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
obj.Actor ! Vehicle.Deconstruct()
|
||||
|
||||
case Mountable.CanDismount(obj: Vehicle, seat_num, _) =>
|
||||
log.info(
|
||||
s"${tplayer.Name} dismounts a ${obj.Definition.asInstanceOf[ObjectDefinition].Name} from seat #$seat_num"
|
||||
)
|
||||
val player_guid: PlanetSideGUID = tplayer.GUID
|
||||
if (player_guid == player.GUID) {
|
||||
//disembarking self
|
||||
log.info(
|
||||
s"${tplayer.Name} dismounts a ${obj.Definition.asInstanceOf[ObjectDefinition].Name} from seat #$seat_num"
|
||||
)
|
||||
ConditionalDriverVehicleControl(obj)
|
||||
UnaccessContainer(obj)
|
||||
DismountAction(tplayer, obj, seat_num)
|
||||
|
|
@ -2742,12 +2767,14 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
//but always seems to return 4 if user is kicked by mount permissions changing
|
||||
sendResponse(DismountVehicleMsg(guid, BailType.Kicked, wasKickedByDriver))
|
||||
if (tplayer_guid == guid) {
|
||||
log.info(s"{${player.Name} has been kicked from ${player.Sex.possessive} ride!")
|
||||
continent.GUID(vehicle_guid) match {
|
||||
val typeOfRide = continent.GUID(vehicle_guid) match {
|
||||
case Some(obj: Vehicle) =>
|
||||
UnaccessContainer(obj)
|
||||
case _ => ;
|
||||
s"the ${obj.Definition.Name}'s seat by ${obj.OwnerName.getOrElse("the pilot")}"
|
||||
case _ =>
|
||||
s"${player.Sex.possessive} ride"
|
||||
}
|
||||
log.info(s"${player.Name} has been kicked from $typeOfRide!")
|
||||
}
|
||||
|
||||
case VehicleResponse.InventoryState2(obj_guid, parent_guid, value) =>
|
||||
|
|
@ -2969,7 +2996,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
cargo: Vehicle,
|
||||
mountPoint: Int
|
||||
): (ObjectAttachMessage, CargoMountPointStatusMessage) = {
|
||||
val msgs @ (attachMessage, mountPointStatusMessage) = CargoBehavior.CargoMountMessages(carrier, cargo, mountPoint)
|
||||
val msgs @ (attachMessage, mountPointStatusMessage) = CarrierBehavior.CargoMountMessages(carrier, cargo, mountPoint)
|
||||
CargoMountMessagesForUs(attachMessage, mountPointStatusMessage)
|
||||
msgs
|
||||
}
|
||||
|
|
@ -3336,8 +3363,8 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
(continent.GUID(cargo_guid), continent.GUID(carrier_guid)) match {
|
||||
case (Some(cargo: Vehicle), Some(carrier: Vehicle)) =>
|
||||
carrier.CargoHolds.find({ case (_, hold) => !hold.isOccupied }) match {
|
||||
case Some((mountPoint, _)) => //try begin the mount process
|
||||
cargo.Actor ! CargoBehavior.CheckCargoMounting(carrier_guid, mountPoint, 0)
|
||||
case Some((mountPoint, _)) =>
|
||||
cargo.Actor ! CargoBehavior.StartCargoMounting(carrier_guid, mountPoint)
|
||||
case _ =>
|
||||
log.warn(
|
||||
s"MountVehicleCargoMsg: ${player.Name} trying to load cargo into a ${carrier.Definition.Name} which oes not have a cargo hold"
|
||||
|
|
@ -3350,17 +3377,11 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
case _ => ;
|
||||
}
|
||||
|
||||
case msg @ DismountVehicleCargoMsg(player_guid, cargo_guid, bailed, requestedByPassenger, kicked) =>
|
||||
case msg @ DismountVehicleCargoMsg(_, cargo_guid, bailed, _, kicked) =>
|
||||
log.debug(s"DismountVehicleCargoMsg: $msg")
|
||||
//when kicked by carrier driver, player_guid will be PlanetSideGUID(0)
|
||||
//when exiting of the cargo vehicle driver's own accord, player_guid will be the cargo vehicle driver
|
||||
continent.GUID(cargo_guid) match {
|
||||
case Some(cargo: Vehicle) if !requestedByPassenger =>
|
||||
continent.GUID(cargo.MountedIn) match {
|
||||
case Some(carrier: Vehicle) =>
|
||||
CargoBehavior.HandleVehicleCargoDismount(continent, cargo_guid, bailed, requestedByPassenger, kicked)
|
||||
case _ => ;
|
||||
}
|
||||
case Some(cargo: Vehicle) =>
|
||||
cargo.Actor ! CargoBehavior.StartCargoDismounting(bailed || kicked)
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
|
|
@ -3633,7 +3654,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
case vehicle if vehicle.CargoHolds.nonEmpty =>
|
||||
vehicle.CargoHolds.collect {
|
||||
case (_index, hold: Cargo) if hold.isOccupied =>
|
||||
CargoBehavior.CargoMountBehaviorForAll(
|
||||
CarrierBehavior.CargoMountBehaviorForAll(
|
||||
vehicle,
|
||||
hold.occupant.get,
|
||||
_index
|
||||
|
|
@ -3779,6 +3800,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
if (isMovingPlus) {
|
||||
CancelZoningProcessWithDescriptiveReason("cancel_motion")
|
||||
}
|
||||
fallHeightTracker(pos.z)
|
||||
// if (is_crouching && !player.Crouching) {
|
||||
// //dev stuff goes here
|
||||
// }
|
||||
|
|
@ -3909,10 +3931,10 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
//we're driving the vehicle
|
||||
persist()
|
||||
turnCounterFunc(player.GUID)
|
||||
fallHeightTracker(pos.z)
|
||||
if (obj.MountedIn.isEmpty) {
|
||||
updateBlockMap(obj, continent, pos)
|
||||
}
|
||||
val seat = obj.Seats(0)
|
||||
player.Position = pos //convenient
|
||||
if (obj.WeaponControlledFromSeat(0).isEmpty) {
|
||||
player.Orientation = Vector3.z(ang.z) //convenient
|
||||
|
|
@ -3969,10 +3991,38 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
KickedByAdministration()
|
||||
}
|
||||
|
||||
case msg @ VehicleSubStateMessage(vehicle_guid, player_guid, vehicle_pos, vehicle_ang, vel, unk1, unk2) =>
|
||||
log.debug(
|
||||
s"VehicleSubState: $vehicle_guid, ${player.Name}_guid, $vehicle_pos, $vehicle_ang, $vel, $unk1, $unk2"
|
||||
)
|
||||
case msg @ VehicleSubStateMessage(vehicle_guid, _, pos, ang, vel, unk1, unk2) =>
|
||||
//log.info(s"msg")
|
||||
ValidObject(vehicle_guid) match {
|
||||
case Some(obj: Vehicle) =>
|
||||
obj.Position = pos
|
||||
obj.Orientation = ang
|
||||
obj.Velocity = vel
|
||||
updateBlockMap(obj, continent, pos)
|
||||
obj.zoneInteractions()
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.VehicleState(
|
||||
player.GUID,
|
||||
vehicle_guid,
|
||||
unk1,
|
||||
pos,
|
||||
ang,
|
||||
obj.Velocity,
|
||||
obj.Flying,
|
||||
0,
|
||||
0,
|
||||
15,
|
||||
false,
|
||||
obj.Cloaked
|
||||
)
|
||||
)
|
||||
|
||||
case _ =>
|
||||
log.warn(
|
||||
s"VehicleSubState: ${player.Name} should not be dispatching this kind of packet for vehicle #${vehicle_guid.guid}"
|
||||
)
|
||||
}
|
||||
|
||||
case msg @ ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, seq, end, target_guid) =>
|
||||
val index = projectile_guid.guid - Projectile.baseUID
|
||||
|
|
@ -5447,7 +5497,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
case Some(obj: Mountable) =>
|
||||
obj.PassengerInSeat(player) match {
|
||||
case Some(seat_num) =>
|
||||
obj.Actor ! Mountable.TryDismount(player, seat_num)
|
||||
obj.Actor ! Mountable.TryDismount(player, seat_num, bailType)
|
||||
if (interstellarFerry.isDefined) {
|
||||
//short-circuit the temporary channel for transferring between zones, the player is no longer doing that
|
||||
//see above in VehicleResponse.TransferPassenger case
|
||||
|
|
@ -5489,7 +5539,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
case (Some(obj: Mountable), Some(tplayer: Player)) =>
|
||||
obj.PassengerInSeat(tplayer) match {
|
||||
case Some(seat_num) =>
|
||||
obj.Actor ! Mountable.TryDismount(tplayer, seat_num)
|
||||
obj.Actor ! Mountable.TryDismount(tplayer, seat_num, bailType)
|
||||
case None =>
|
||||
dismountWarning(
|
||||
s"DismountVehicleMsg: can not find where other player ${player.Name}_guid is seated in mountable $obj_guid"
|
||||
|
|
@ -5552,8 +5602,111 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
squadService ! SquadServiceMessage(player, continent, SquadServiceAction.Waypoint(request, wtype, unk, info))
|
||||
}
|
||||
|
||||
case msg @ GenericCollisionMsg(u1, p, t, php, thp, pv, tv, ppos, tpos, u2, u3, u4) =>
|
||||
log.info(s"${player.Name} would be in intense and excruciating pain right now if collision worked")
|
||||
case msg @ GenericCollisionMsg(ctype, p, php, ppos, pv, t, thp, tpos, tv, u1, u2, u3) =>
|
||||
//log.info(s"$msg")
|
||||
val fallHeight = {
|
||||
if (pv.z * pv.z >= (pv.x * pv.x + pv.y * pv.y) * 0.5f) {
|
||||
if (heightTrend) {
|
||||
val fall = heightLast - heightHistory
|
||||
heightHistory = heightLast
|
||||
fall
|
||||
}
|
||||
else {
|
||||
val fall = heightHistory - heightLast
|
||||
heightLast = heightHistory
|
||||
fall
|
||||
}
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
}
|
||||
val (target1, target2, bailProtectStatus, velocity) = (ctype, ValidObject(p)) match {
|
||||
case (CollisionIs.OfInfantry, out @ Some(user: Player))
|
||||
if user == player =>
|
||||
val bailStatus = session.flying || player.spectator || session.speed > 1f || player.BailProtection
|
||||
player.BailProtection = false
|
||||
val v = if (player.avatar.implants.exists {
|
||||
case Some(implant) => implant.definition.implantType == ImplantType.Surge && implant.active
|
||||
case _ => false
|
||||
}) {
|
||||
Vector3.Zero
|
||||
} else {
|
||||
pv
|
||||
}
|
||||
(out, None, bailStatus, v)
|
||||
case (CollisionIs.OfGroundVehicle, out @ Some(v: Vehicle))
|
||||
if v.Seats(0).occupant.contains(player) =>
|
||||
val bailStatus = v.BailProtection
|
||||
v.BailProtection = false
|
||||
(out, ValidObject(t), bailStatus, pv)
|
||||
case (CollisionIs.OfAircraft, out @ Some(v: Vehicle))
|
||||
if v.Definition.CanFly && v.Seats(0).occupant.contains(player) =>
|
||||
(out, ValidObject(t), false, pv)
|
||||
case _ =>
|
||||
(None, None, false, Vector3.Zero)
|
||||
}
|
||||
val curr = System.currentTimeMillis()
|
||||
(target1, t, target2) match {
|
||||
case (None, _, _) => ;
|
||||
|
||||
case (Some(us: PlanetSideServerObject with Vitality with FactionAffinity), PlanetSideGUID(0), _) =>
|
||||
if (collisionHistory.get(us.Actor) match {
|
||||
case Some(lastCollision) if curr - lastCollision <= 1000L =>
|
||||
false
|
||||
case _ =>
|
||||
collisionHistory.put(us.Actor, curr)
|
||||
true
|
||||
}) {
|
||||
if (!bailProtectStatus) {
|
||||
HandleDealingDamage(
|
||||
us,
|
||||
DamageInteraction(
|
||||
SourceEntry(us),
|
||||
CollisionReason(velocity, fallHeight, us.DamageModel),
|
||||
ppos
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
case (
|
||||
Some(us: PlanetSideServerObject with Vitality with FactionAffinity), _,
|
||||
Some(victim: PlanetSideServerObject with Vitality with FactionAffinity)
|
||||
) =>
|
||||
if (collisionHistory.get(victim.Actor) match {
|
||||
case Some(lastCollision) if curr - lastCollision <= 1000L =>
|
||||
false
|
||||
case _ =>
|
||||
collisionHistory.put(victim.Actor, curr)
|
||||
true
|
||||
}) {
|
||||
val usSource = SourceEntry(us)
|
||||
val victimSource = SourceEntry(victim)
|
||||
//we take damage from the collision
|
||||
if (!bailProtectStatus) {
|
||||
HandleDealingDamage(
|
||||
us,
|
||||
DamageInteraction(
|
||||
usSource,
|
||||
CollisionWithReason(CollisionReason(velocity - tv, fallHeight, us.DamageModel), victimSource),
|
||||
ppos
|
||||
)
|
||||
)
|
||||
}
|
||||
//get dealt damage from our own collision (no protection)
|
||||
collisionHistory.put(us.Actor, curr)
|
||||
HandleDealingDamage(
|
||||
victim,
|
||||
DamageInteraction(
|
||||
victimSource,
|
||||
CollisionWithReason(CollisionReason(tv - velocity, 0, victim.DamageModel), usSource),
|
||||
tpos
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
case msg @ BugReportMessage(
|
||||
version_major,
|
||||
|
|
@ -6760,6 +6913,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
progressBarValue = None
|
||||
lastTerminalOrderFulfillment = true
|
||||
kitToBeUsed = None
|
||||
collisionHistory.clear()
|
||||
accessedContainer match {
|
||||
case Some(v: Vehicle) =>
|
||||
val vguid = v.GUID
|
||||
|
|
@ -6863,7 +7017,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
)
|
||||
carrierInfo match {
|
||||
case (Some(carrier), Some((index, _))) =>
|
||||
CargoBehavior.CargoMountBehaviorForOthers(carrier, vehicle, index, player.GUID)
|
||||
CarrierBehavior.CargoMountBehaviorForOthers(carrier, vehicle, index, player.GUID)
|
||||
case _ =>
|
||||
vehicle.MountedIn = None
|
||||
}
|
||||
|
|
@ -7725,10 +7879,15 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
def DismountAction(tplayer: Player, obj: PlanetSideGameObject with Mountable, seatNum: Int): Unit = {
|
||||
val player_guid: PlanetSideGUID = tplayer.GUID
|
||||
keepAliveFunc = NormalKeepAlive
|
||||
sendResponse(DismountVehicleMsg(player_guid, BailType.Normal, wasKickedByDriver = false))
|
||||
val bailType = if (tplayer.BailProtection) {
|
||||
BailType.Bailed
|
||||
} else {
|
||||
BailType.Normal
|
||||
}
|
||||
sendResponse(DismountVehicleMsg(player_guid, bailType, wasKickedByDriver = false))
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.DismountVehicle(player_guid, BailType.Normal, false)
|
||||
VehicleAction.DismountVehicle(player_guid, bailType, false)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -7748,18 +7907,41 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
val func = data.calculate()
|
||||
target match {
|
||||
case obj: Player if obj.CanDamage && obj.Actor != Default.Actor =>
|
||||
log.info(s"${player.Name} is attacking ${obj.Name}")
|
||||
if (obj.CharId != player.CharId) {
|
||||
log.info(s"${player.Name} is attacking ${obj.Name}")
|
||||
} else {
|
||||
log.info(s"${player.Name} hurt ${player.Sex.pronounObject}self")
|
||||
}
|
||||
// auto kick players damaging spectators
|
||||
if (obj.spectator && obj != player) {
|
||||
AdministrativeKick(player)
|
||||
} else {
|
||||
obj.Actor ! Vitality.Damage(func)
|
||||
}
|
||||
|
||||
case obj: Vehicle if obj.CanDamage =>
|
||||
log.info(s"${player.Name} is attacking ${obj.OwnerName.getOrElse("someone")}'s ${obj.Definition.Name}")
|
||||
val name = player.Name
|
||||
val ownerName = obj.OwnerName.getOrElse("someone")
|
||||
if (ownerName.equals(name)) {
|
||||
log.info(s"$name is damaging ${player.Sex.possessive} own ${obj.Definition.Name}")
|
||||
} else {
|
||||
log.info(s"$name is attacking $ownerName's ${obj.Definition.Name}")
|
||||
}
|
||||
obj.Actor ! Vitality.Damage(func)
|
||||
case obj: Amenity if obj.CanDamage => obj.Actor ! Vitality.Damage(func)
|
||||
case obj: Deployable if obj.CanDamage => obj.Actor ! Vitality.Damage(func)
|
||||
|
||||
case obj: Amenity if obj.CanDamage =>
|
||||
obj.Actor ! Vitality.Damage(func)
|
||||
|
||||
case obj: Deployable if obj.CanDamage =>
|
||||
val name = player.Name
|
||||
val ownerName = obj.OwnerName.getOrElse("someone")
|
||||
if (ownerName.equals(name)) {
|
||||
log.info(s"$name is damaging ${player.Sex.possessive} own ${obj.Definition.Name}")
|
||||
} else {
|
||||
log.info(s"$name is attacking $ownerName's ${obj.Definition.Name}")
|
||||
}
|
||||
obj.Actor ! Vitality.Damage(func)
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
|
@ -8197,7 +8379,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
log.warn(
|
||||
s"LoadZoneInVehicleAsDriver: ${player.Name} must eject cargo in hold $index; vehicle is missing driver"
|
||||
)
|
||||
CargoBehavior.HandleVehicleCargoDismount(cargo.GUID, cargo, vehicle.GUID, vehicle, false, false, true)
|
||||
cargo.Actor ! CargoBehavior.StartCargoDismounting(bailed = false)
|
||||
case entry =>
|
||||
val cargo = vehicle.CargoHolds(entry.mount).occupant.get
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
|
|
@ -9139,26 +9321,28 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
|
||||
var oldRefsMap: mutable.HashMap[PlanetSideGUID, String] = new mutable.HashMap[PlanetSideGUID, String]()
|
||||
def updateOldRefsMap(): Unit = {
|
||||
oldRefsMap.addAll(
|
||||
(continent.GUID(player.VehicleSeated) match {
|
||||
case Some(v: Vehicle) =>
|
||||
v.Weapons.toList.collect {
|
||||
case (_, slot: EquipmentSlot) if slot.Equipment.nonEmpty => updateOldRefsMap(slot.Equipment.get)
|
||||
}.flatten ++
|
||||
updateOldRefsMap(v.Inventory)
|
||||
case _ =>
|
||||
Map.empty[PlanetSideGUID, String]
|
||||
}) ++
|
||||
(accessedContainer match {
|
||||
case Some(cont) => updateOldRefsMap(cont.Inventory)
|
||||
case None => Map.empty[PlanetSideGUID, String]
|
||||
}) ++
|
||||
player.Holsters().toList.collect {
|
||||
case slot if slot.Equipment.nonEmpty => updateOldRefsMap(slot.Equipment.get)
|
||||
}.flatten ++
|
||||
updateOldRefsMap(player.Inventory) ++
|
||||
updateOldRefsMap(player.avatar.locker.Inventory)
|
||||
)
|
||||
if(player.HasGUID) {
|
||||
oldRefsMap.addAll(
|
||||
(continent.GUID(player.VehicleSeated) match {
|
||||
case Some(v : Vehicle) =>
|
||||
v.Weapons.toList.collect {
|
||||
case (_, slot : EquipmentSlot) if slot.Equipment.nonEmpty => updateOldRefsMap(slot.Equipment.get)
|
||||
}.flatten ++
|
||||
updateOldRefsMap(v.Inventory)
|
||||
case _ =>
|
||||
Map.empty[PlanetSideGUID, String]
|
||||
}) ++
|
||||
(accessedContainer match {
|
||||
case Some(cont) => updateOldRefsMap(cont.Inventory)
|
||||
case None => Map.empty[PlanetSideGUID, String]
|
||||
}) ++
|
||||
player.Holsters().toList.collect {
|
||||
case slot if slot.Equipment.nonEmpty => updateOldRefsMap(slot.Equipment.get)
|
||||
}.flatten ++
|
||||
updateOldRefsMap(player.Inventory) ++
|
||||
updateOldRefsMap(player.avatar.locker.Inventory)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def updateOldRefsMap(inventory: net.psforever.objects.inventory.GridInventory): IterableOnce[(PlanetSideGUID, String)] = {
|
||||
|
|
@ -9179,6 +9363,21 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
}
|
||||
}
|
||||
|
||||
def fallHeightTracker(zHeight: Float): Unit = {
|
||||
if ((heightTrend && heightLast - zHeight >= 0.5f) ||
|
||||
(!heightTrend && zHeight - heightLast >= 0.5f)) {
|
||||
heightTrend = !heightTrend
|
||||
// if (heightTrend) {
|
||||
// GetMountableAndSeat(None, player, continent) match {
|
||||
// case (Some(v: Vehicle), _) => v.BailProtection = false
|
||||
// case _ => player.BailProtection = false
|
||||
// }
|
||||
// }
|
||||
heightHistory = zHeight
|
||||
}
|
||||
heightLast = zHeight
|
||||
}
|
||||
|
||||
def failWithError(error: String) = {
|
||||
log.error(error)
|
||||
middlewareActor ! MiddlewareActor.Teardown()
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ class ZoneActor(context: ActorContext[ZoneActor.Command], zone: Zone)
|
|||
zone.blockMap.addTo(target, toPosition)
|
||||
|
||||
case UpdateBlockMap(target, toPosition) =>
|
||||
zone.blockMap.move(target, toPosition)
|
||||
target.updateBlockMapEntry(toPosition)
|
||||
|
||||
case RemoveFromBlockMap(target) =>
|
||||
zone.blockMap.removeFrom(target)
|
||||
|
|
|
|||
|
|
@ -146,19 +146,30 @@ object Deployables {
|
|||
* If the default ammunition mode for the `ConstructionTool` is not supported by the given certifications,
|
||||
* find a suitable ammunition mode and switch to it internally.
|
||||
* No special complaint is raised if the `ConstructionItem` itself is completely unsupported.
|
||||
* The search function will explore every ammo option for every fire mode option
|
||||
* and will stop when it finds either a valid option or when arrives back at the original fire mode.
|
||||
* @param certs the certification baseline being compared against
|
||||
* @param obj the `ConstructionItem` entity
|
||||
* @return `true`, if the ammunition mode of the item has been changed;
|
||||
* @return `true`, if the firemode and ammunition mode of the item is valid;
|
||||
* `false`, otherwise
|
||||
*/
|
||||
def initializeConstructionAmmoMode(
|
||||
certs: Set[Certification],
|
||||
obj: ConstructionItem
|
||||
): Boolean = {
|
||||
def initializeConstructionItem(
|
||||
certs: Set[Certification],
|
||||
obj: ConstructionItem
|
||||
): Boolean = {
|
||||
val initialFireModeIndex = obj.FireModeIndex
|
||||
if (!Deployables.constructionItemPermissionComparison(certs, obj.ModePermissions)) {
|
||||
Deployables.performConstructionItemAmmoChange(certs, obj, obj.AmmoTypeIndex)
|
||||
while (!Deployables.constructionItemPermissionComparison(certs, obj.ModePermissions) &&
|
||||
!Deployables.performConstructionItemAmmoChange(certs, obj, obj.AmmoTypeIndex) &&
|
||||
{
|
||||
obj.NextFireMode
|
||||
initialFireModeIndex != obj.FireModeIndex
|
||||
}) {
|
||||
/* change in fire mode occurs in conditional */
|
||||
}
|
||||
Deployables.constructionItemPermissionComparison(certs, obj.ModePermissions)
|
||||
} else {
|
||||
false
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,22 +31,28 @@ import net.psforever.objects.vital.damage._
|
|||
import net.psforever.objects.vital.etc.ExplodingRadialDegrade
|
||||
import net.psforever.objects.vital.projectile._
|
||||
import net.psforever.objects.vital.prop.DamageWithPosition
|
||||
import net.psforever.objects.vital.{ComplexDeployableResolutions, MaxResolutions, SimpleResolutions}
|
||||
import net.psforever.objects.vital._
|
||||
import net.psforever.types.{ExoSuitType, ImplantType, PlanetSideEmpire, Vector3}
|
||||
import net.psforever.types._
|
||||
import net.psforever.objects.serverobject.llu.{CaptureFlagDefinition, CaptureFlagSocketDefinition}
|
||||
import net.psforever.objects.vital.collision.TrapCollisionDamageMultiplier
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.duration._
|
||||
|
||||
object GlobalDefinitions {
|
||||
// Characters
|
||||
/*
|
||||
characters
|
||||
*/
|
||||
val avatar = new AvatarDefinition(121)
|
||||
avatar.MaxHealth = 100
|
||||
avatar.Damageable = true
|
||||
avatar.DrownAtMaxDepth = true
|
||||
avatar.MaxDepth = 1.609375f //Male, standing, not MAX
|
||||
avatar.UnderwaterLifespan(suffocation = 60000L, recovery = 10000L)
|
||||
avatar.collision.xy = CollisionXYData(Array((0.1f, 0), (0.2f, 5), (0.50f, 15), (0.75f, 20), (1f, 30))) //not in the ADB
|
||||
avatar.collision.z = CollisionZData(Array((0.1f, 0), (5f, 1), (10f, 3), (20f, 5), (35f, 7), (50f, 10), (75f, 40), (100f, 100))) //not in the ADB
|
||||
avatar.maxForwardSpeed = 27f //not in the ADB; running speed
|
||||
/*
|
||||
exo-suits
|
||||
*/
|
||||
|
|
@ -1704,6 +1710,8 @@ object GlobalDefinitions {
|
|||
Standard.ResistanceDirectHit = 4
|
||||
Standard.ResistanceSplash = 15
|
||||
Standard.ResistanceAggravated = 8
|
||||
Standard.collision.forceFactor = 1.5f
|
||||
Standard.collision.massFactor = 2f
|
||||
|
||||
Agile.Name = "lite_armor"
|
||||
Agile.Descriptor = "agile"
|
||||
|
|
@ -1717,6 +1725,8 @@ object GlobalDefinitions {
|
|||
Agile.ResistanceDirectHit = 6
|
||||
Agile.ResistanceSplash = 25
|
||||
Agile.ResistanceAggravated = 10
|
||||
Agile.collision.forceFactor = 1.5f
|
||||
Agile.collision.massFactor = 2f
|
||||
|
||||
Reinforced.Name = "med_armor"
|
||||
Reinforced.Descriptor = "reinforced"
|
||||
|
|
@ -1732,6 +1742,8 @@ object GlobalDefinitions {
|
|||
Reinforced.ResistanceDirectHit = 10
|
||||
Reinforced.ResistanceSplash = 35
|
||||
Reinforced.ResistanceAggravated = 12
|
||||
Reinforced.collision.forceFactor = 2f
|
||||
Reinforced.collision.massFactor = 3f
|
||||
|
||||
Infiltration.Name = "infiltration_suit"
|
||||
Infiltration.Permissions = List(Certification.InfiltrationSuit)
|
||||
|
|
@ -1752,6 +1764,8 @@ object GlobalDefinitions {
|
|||
max.ResistanceDirectHit = 6
|
||||
max.ResistanceSplash = 35
|
||||
max.ResistanceAggravated = 10
|
||||
max.collision.forceFactor = 4f
|
||||
max.collision.massFactor = 10f
|
||||
max.DamageUsing = DamageCalculations.AgainstMaxSuit
|
||||
max.Model = MaxResolutions.calculate
|
||||
}
|
||||
|
|
@ -5652,10 +5666,17 @@ object GlobalDefinitions {
|
|||
* Initialize `VehicleDefinition` globals.
|
||||
*/
|
||||
private def init_vehicles(): Unit = {
|
||||
init_ground_vehicles()
|
||||
init_flight_vehicles()
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize land-based `VehicleDefinition` globals.
|
||||
*/
|
||||
private def init_ground_vehicles(): Unit = {
|
||||
val atvForm = GeometryForm.representByCylinder(radius = 1.1797f, height = 1.1875f) _
|
||||
val delivererForm = GeometryForm.representByCylinder(radius = 2.46095f, height = 2.40626f) _ //TODO hexahedron
|
||||
val apcForm = GeometryForm.representByCylinder(radius = 4.6211f, height = 3.90626f) _ //TODO hexahedron
|
||||
val liberatorForm = GeometryForm.representByCylinder(radius = 3.74615f, height = 2.51563f) _
|
||||
|
||||
val bailableSeat = new SeatDefinition() {
|
||||
bailable = true
|
||||
|
|
@ -5694,6 +5715,11 @@ object GlobalDefinitions {
|
|||
fury.MaxDepth = 1.3f
|
||||
fury.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||
fury.Geometry = atvForm
|
||||
fury.collision.avatarCollisionDamageMax = 35
|
||||
fury.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 5), (0.5f, 20), (0.75f, 40), (1f, 60)))
|
||||
fury.collision.z = CollisionZData(Array((8f, 1), (24f, 35), (40f, 100), (48f, 175), (52f, 350)))
|
||||
fury.maxForwardSpeed = 90f
|
||||
fury.mass = 32.1f
|
||||
|
||||
quadassault.Name = "quadassault" // Basilisk
|
||||
quadassault.MaxHealth = 650
|
||||
|
|
@ -5725,6 +5751,11 @@ object GlobalDefinitions {
|
|||
quadassault.MaxDepth = 1.3f
|
||||
quadassault.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||
quadassault.Geometry = atvForm
|
||||
quadassault.collision.avatarCollisionDamageMax = 35
|
||||
quadassault.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 5), (0.5f, 20), (0.75f, 40), (1f, 60)))
|
||||
quadassault.collision.z = CollisionZData(Array((8f, 1), (24f, 35), (40f, 100), (48f, 175), (52f, 350)))
|
||||
quadassault.maxForwardSpeed = 90f
|
||||
quadassault.mass = 32.1f
|
||||
|
||||
quadstealth.Name = "quadstealth" // Wraith
|
||||
quadstealth.MaxHealth = 650
|
||||
|
|
@ -5756,6 +5787,11 @@ object GlobalDefinitions {
|
|||
quadstealth.MaxDepth = 1.25f
|
||||
quadstealth.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||
quadstealth.Geometry = atvForm
|
||||
quadstealth.collision.avatarCollisionDamageMax = 35
|
||||
quadstealth.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 5), (0.5f, 20), (0.75f, 40), (1f, 60)))
|
||||
quadstealth.collision.z = CollisionZData(Array((8f, 1), (24f, 35), (40f, 100), (48f, 175), (52f, 350)))
|
||||
quadstealth.maxForwardSpeed = 90f
|
||||
quadstealth.mass = 32.1f
|
||||
|
||||
two_man_assault_buggy.Name = "two_man_assault_buggy" // Harasser
|
||||
two_man_assault_buggy.MaxHealth = 1250
|
||||
|
|
@ -5788,6 +5824,11 @@ object GlobalDefinitions {
|
|||
two_man_assault_buggy.MaxDepth = 1.5f
|
||||
two_man_assault_buggy.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||
two_man_assault_buggy.Geometry = GeometryForm.representByCylinder(radius = 2.10545f, height = 1.59376f)
|
||||
two_man_assault_buggy.collision.avatarCollisionDamageMax = 75
|
||||
two_man_assault_buggy.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 5), (0.5f, 20), (0.75f, 40), (1f, 60)))
|
||||
two_man_assault_buggy.collision.z = CollisionZData(Array((7f, 1), (21f, 50), (35f, 150), (42f, 300), (45.5f, 600)))
|
||||
two_man_assault_buggy.maxForwardSpeed = 85f
|
||||
two_man_assault_buggy.mass = 52.4f
|
||||
|
||||
skyguard.Name = "skyguard"
|
||||
skyguard.MaxHealth = 1000
|
||||
|
|
@ -5821,6 +5862,11 @@ object GlobalDefinitions {
|
|||
skyguard.MaxDepth = 1.5f
|
||||
skyguard.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||
skyguard.Geometry = GeometryForm.representByCylinder(radius = 1.8867f, height = 1.4375f)
|
||||
skyguard.collision.avatarCollisionDamageMax = 100
|
||||
skyguard.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 5), (0.5f, 20), (0.75f, 40), (1f, 60)))
|
||||
skyguard.collision.z = CollisionZData(Array((7f, 1), (21f, 50), (35f, 150), (42f, 300), (45.4f, 600)))
|
||||
skyguard.maxForwardSpeed = 90f
|
||||
skyguard.mass = 78.9f
|
||||
|
||||
threemanheavybuggy.Name = "threemanheavybuggy" // Marauder
|
||||
threemanheavybuggy.MaxHealth = 1700
|
||||
|
|
@ -5858,6 +5904,11 @@ object GlobalDefinitions {
|
|||
threemanheavybuggy.MaxDepth = 1.83f
|
||||
threemanheavybuggy.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||
threemanheavybuggy.Geometry = GeometryForm.representByCylinder(radius = 2.1953f, height = 2.03125f)
|
||||
threemanheavybuggy.collision.avatarCollisionDamageMax = 100
|
||||
threemanheavybuggy.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 15), (0.5f, 30), (0.75f, 60), (1f, 80)))
|
||||
threemanheavybuggy.collision.z = CollisionZData(Array((6f, 1), (18f, 50), (30f, 150), (36f, 300), (39f, 900)))
|
||||
threemanheavybuggy.maxForwardSpeed = 80f
|
||||
threemanheavybuggy.mass = 96.3f
|
||||
|
||||
twomanheavybuggy.Name = "twomanheavybuggy" // Enforcer
|
||||
twomanheavybuggy.MaxHealth = 1800
|
||||
|
|
@ -5891,6 +5942,11 @@ object GlobalDefinitions {
|
|||
twomanheavybuggy.MaxDepth = 1.95f
|
||||
twomanheavybuggy.UnderwaterLifespan(suffocation = 5000L, recovery = 2500L)
|
||||
twomanheavybuggy.Geometry = GeometryForm.representByCylinder(radius = 2.60935f, height = 1.79688f)
|
||||
twomanheavybuggy.collision.avatarCollisionDamageMax = 100
|
||||
twomanheavybuggy.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 12), (0.5f, 30), (0.75f, 55), (1f, 80)))
|
||||
twomanheavybuggy.collision.z = CollisionZData(Array((6f, 1), (18f, 50), (30f, 150), (36f, 300), (39f, 900)))
|
||||
twomanheavybuggy.maxForwardSpeed = 80f
|
||||
twomanheavybuggy.mass = 83.2f
|
||||
|
||||
twomanhoverbuggy.Name = "twomanhoverbuggy" // Thresher
|
||||
twomanhoverbuggy.MaxHealth = 1600
|
||||
|
|
@ -5926,6 +5982,11 @@ object GlobalDefinitions {
|
|||
recovery = 5000L
|
||||
) //but the thresher hovers over water, so ...?
|
||||
twomanhoverbuggy.Geometry = GeometryForm.representByCylinder(radius = 2.1875f, height = 2.01563f)
|
||||
twomanhoverbuggy.collision.avatarCollisionDamageMax = 125
|
||||
twomanhoverbuggy.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 13), (0.5f, 35), (0.75f, 65), (1f, 90)))
|
||||
twomanhoverbuggy.collision.z = CollisionZData(Array((6f, 1), (18f, 50), (30f, 150), (36f, 300), (39f, 900)))
|
||||
twomanhoverbuggy.maxForwardSpeed = 85f
|
||||
twomanhoverbuggy.mass = 55.5f
|
||||
|
||||
mediumtransport.Name = "mediumtransport" // Deliverer
|
||||
mediumtransport.MaxHealth = 2500
|
||||
|
|
@ -5969,6 +6030,11 @@ object GlobalDefinitions {
|
|||
mediumtransport.MaxDepth = 1.2f
|
||||
mediumtransport.UnderwaterLifespan(suffocation = -1, recovery = -1)
|
||||
mediumtransport.Geometry = delivererForm
|
||||
mediumtransport.collision.avatarCollisionDamageMax = 120
|
||||
mediumtransport.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 35), (0.5f, 60), (0.75f, 110), (1f, 175)))
|
||||
mediumtransport.collision.z = CollisionZData(Array((5f, 1), (15f, 50), (25f, 200), (30f, 750), (32.5f, 2000)))
|
||||
mediumtransport.maxForwardSpeed = 70f
|
||||
mediumtransport.mass = 108.5f
|
||||
|
||||
battlewagon.Name = "battlewagon" // Raider
|
||||
battlewagon.MaxHealth = 2500
|
||||
|
|
@ -6015,6 +6081,11 @@ object GlobalDefinitions {
|
|||
battlewagon.MaxDepth = 1.2f
|
||||
battlewagon.UnderwaterLifespan(suffocation = -1, recovery = -1)
|
||||
battlewagon.Geometry = delivererForm
|
||||
battlewagon.collision.avatarCollisionDamageMax = 120
|
||||
battlewagon.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 35), (0.5f, 60), (0.75f, 110), (1f, 175))) //inherited from mediumtransport
|
||||
battlewagon.collision.z = CollisionZData(Array((5f, 1), (15f, 50), (25f, 200), (30f, 750), (32.5f, 2000))) //inherited from mediumtransport
|
||||
battlewagon.maxForwardSpeed = 65f
|
||||
battlewagon.mass = 108.5f
|
||||
|
||||
thunderer.Name = "thunderer"
|
||||
thunderer.MaxHealth = 2500
|
||||
|
|
@ -6058,6 +6129,11 @@ object GlobalDefinitions {
|
|||
thunderer.MaxDepth = 1.2f
|
||||
thunderer.UnderwaterLifespan(suffocation = -1, recovery = -1)
|
||||
thunderer.Geometry = delivererForm
|
||||
thunderer.collision.avatarCollisionDamageMax = 120
|
||||
thunderer.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 35), (0.5f, 60), (0.75f, 110), (1f, 175)))
|
||||
thunderer.collision.z = CollisionZData(Array((5f, 1), (15f, 50), (25f, 200), (30f, 750), (32.5f, 2000)))
|
||||
thunderer.maxForwardSpeed = 65f
|
||||
thunderer.mass = 108.5f
|
||||
|
||||
aurora.Name = "aurora"
|
||||
aurora.MaxHealth = 2500
|
||||
|
|
@ -6101,6 +6177,11 @@ object GlobalDefinitions {
|
|||
aurora.MaxDepth = 1.2f
|
||||
aurora.UnderwaterLifespan(suffocation = -1, recovery = -1)
|
||||
aurora.Geometry = delivererForm
|
||||
aurora.collision.avatarCollisionDamageMax = 120
|
||||
aurora.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 35), (0.5f, 60), (0.75f, 110), (1f, 175)))
|
||||
aurora.collision.z = CollisionZData(Array((5f, 1), (15f, 50), (25f, 200), (30f, 750), (32.5f, 2000)))
|
||||
aurora.maxForwardSpeed = 65f
|
||||
aurora.mass = 108.5f
|
||||
|
||||
apc_tr.Name = "apc_tr" // Juggernaut
|
||||
apc_tr.MaxHealth = 6000
|
||||
|
|
@ -6164,6 +6245,11 @@ object GlobalDefinitions {
|
|||
apc_tr.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
|
||||
apc_tr.Geometry = apcForm
|
||||
apc_tr.MaxCapacitor = 300
|
||||
apc_tr.collision.avatarCollisionDamageMax = 300
|
||||
apc_tr.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 10), (0.5f, 40), (0.75f, 70), (1f, 110)))
|
||||
apc_tr.collision.z = CollisionZData(Array((2f, 1), (6f, 50), (10f, 300), (12f, 1000), (13f, 3000)))
|
||||
apc_tr.maxForwardSpeed = 60f
|
||||
apc_tr.mass = 128.4f
|
||||
|
||||
apc_nc.Name = "apc_nc" // Vindicator
|
||||
apc_nc.MaxHealth = 6000
|
||||
|
|
@ -6227,6 +6313,11 @@ object GlobalDefinitions {
|
|||
apc_nc.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
|
||||
apc_nc.Geometry = apcForm
|
||||
apc_nc.MaxCapacitor = 300
|
||||
apc_nc.collision.avatarCollisionDamageMax = 300
|
||||
apc_nc.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 10), (0.5f, 40), (0.75f, 70), (1f, 110)))
|
||||
apc_nc.collision.z = CollisionZData(Array((2f, 1), (6f, 50), (10f, 300), (12f, 1000), (13f, 3000)))
|
||||
apc_nc.maxForwardSpeed = 60f
|
||||
apc_nc.mass = 128.4f
|
||||
|
||||
apc_vs.Name = "apc_vs" // Leviathan
|
||||
apc_vs.MaxHealth = 6000
|
||||
|
|
@ -6290,6 +6381,11 @@ object GlobalDefinitions {
|
|||
apc_vs.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
|
||||
apc_vs.Geometry = apcForm
|
||||
apc_vs.MaxCapacitor = 300
|
||||
apc_vs.collision.avatarCollisionDamageMax = 300
|
||||
apc_vs.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 10), (0.5f, 40), (0.75f, 70), (1f, 110)))
|
||||
apc_vs.collision.z = CollisionZData(Array((2f, 1), (6f, 50), (10f, 300), (12f, 1000), (13f, 3000)))
|
||||
apc_vs.maxForwardSpeed = 60f
|
||||
apc_vs.mass = 128.4f
|
||||
|
||||
lightning.Name = "lightning"
|
||||
lightning.MaxHealth = 2000
|
||||
|
|
@ -6324,6 +6420,11 @@ object GlobalDefinitions {
|
|||
lightning.MaxDepth = 1.38f
|
||||
lightning.UnderwaterLifespan(suffocation = 12000L, recovery = 6000L)
|
||||
lightning.Geometry = GeometryForm.representByCylinder(radius = 2.5078f, height = 1.79688f)
|
||||
lightning.collision.avatarCollisionDamageMax = 150
|
||||
lightning.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 10), (0.5f, 25), (0.75f, 50), (1f, 80)))
|
||||
lightning.collision.z = CollisionZData(Array((6f, 1), (18f, 50), (30f, 150), (36f, 300), (39f, 750)))
|
||||
lightning.maxForwardSpeed = 74f
|
||||
lightning.mass = 100.2f
|
||||
|
||||
prowler.Name = "prowler"
|
||||
prowler.MaxHealth = 4800
|
||||
|
|
@ -6363,6 +6464,11 @@ object GlobalDefinitions {
|
|||
prowler.MaxDepth = 3
|
||||
prowler.UnderwaterLifespan(suffocation = 12000L, recovery = 6000L)
|
||||
prowler.Geometry = GeometryForm.representByCylinder(radius = 3.461f, height = 3.48438f)
|
||||
prowler.collision.avatarCollisionDamageMax = 300
|
||||
prowler.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 15), (0.5f, 40), (0.75f, 75), (1f, 100)))
|
||||
prowler.collision.z = CollisionZData(Array((5f, 1), (15f, 50), (25f, 250), (30f, 600), (32.5f, 1500)))
|
||||
prowler.maxForwardSpeed = 57f
|
||||
prowler.mass = 510.5f
|
||||
|
||||
vanguard.Name = "vanguard"
|
||||
vanguard.MaxHealth = 5400
|
||||
|
|
@ -6398,6 +6504,11 @@ object GlobalDefinitions {
|
|||
vanguard.MaxDepth = 2.7f
|
||||
vanguard.UnderwaterLifespan(suffocation = 12000L, recovery = 6000L)
|
||||
vanguard.Geometry = GeometryForm.representByCylinder(radius = 3.8554f, height = 2.60938f)
|
||||
vanguard.collision.avatarCollisionDamageMax = 300
|
||||
vanguard.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 5), (0.5f, 20), (0.75f, 40), (1f, 60)))
|
||||
vanguard.collision.z = CollisionZData(Array((5f, 1), (15f, 50), (25f, 100), (30f, 250), (32.5f, 600)))
|
||||
vanguard.maxForwardSpeed = 60f
|
||||
vanguard.mass = 460.4f
|
||||
|
||||
magrider.Name = "magrider"
|
||||
magrider.MaxHealth = 4200
|
||||
|
|
@ -6435,6 +6546,11 @@ object GlobalDefinitions {
|
|||
magrider.MaxDepth = 2
|
||||
magrider.UnderwaterLifespan(suffocation = 45000L, recovery = 5000L) //but the magrider hovers over water, so ...?
|
||||
magrider.Geometry = GeometryForm.representByCylinder(radius = 3.3008f, height = 3.26562f)
|
||||
magrider.collision.avatarCollisionDamageMax = 225
|
||||
magrider.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 35), (0.5f, 70), (0.75f, 90), (1f, 120)))
|
||||
magrider.collision.z = CollisionZData(Array((5f, 1), (15f, 50), (25f, 250), (30f, 600), (32.5f, 1500)))
|
||||
magrider.maxForwardSpeed = 65f
|
||||
magrider.mass = 75.3f
|
||||
|
||||
val utilityConverter = new UtilityVehicleConverter
|
||||
ant.Name = "ant"
|
||||
|
|
@ -6470,6 +6586,11 @@ object GlobalDefinitions {
|
|||
ant.MaxDepth = 2
|
||||
ant.UnderwaterLifespan(suffocation = 12000L, recovery = 6000L)
|
||||
ant.Geometry = GeometryForm.representByCylinder(radius = 2.16795f, height = 2.09376f) //TODO hexahedron
|
||||
ant.collision.avatarCollisionDamageMax = 50
|
||||
ant.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 10), (0.5f, 30), (0.75f, 50), (1f, 70)))
|
||||
ant.collision.z = CollisionZData(Array((2f, 1), (6f, 50), (10f, 250), (12f, 500), (13f, 750)))
|
||||
ant.maxForwardSpeed = 65f
|
||||
ant.mass = 80.5f
|
||||
|
||||
ams.Name = "ams"
|
||||
ams.MaxHealth = 3000
|
||||
|
|
@ -6508,6 +6629,11 @@ object GlobalDefinitions {
|
|||
ams.MaxDepth = 3
|
||||
ams.UnderwaterLifespan(suffocation = 5000L, recovery = 5000L)
|
||||
ams.Geometry = GeometryForm.representByCylinder(radius = 3.0117f, height = 3.39062f) //TODO hexahedron
|
||||
ams.collision.avatarCollisionDamageMax = 250
|
||||
ams.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 10), (0.5f, 40), (0.75f, 60), (1f, 100)))
|
||||
ams.collision.z = CollisionZData(Array((2f, 1), (6f, 50), (10f, 250), (12f, 805), (13f, 3000)))
|
||||
ams.maxForwardSpeed = 70f
|
||||
ams.mass = 136.8f
|
||||
|
||||
val variantConverter = new VariantVehicleConverter
|
||||
router.Name = "router"
|
||||
|
|
@ -6543,8 +6669,13 @@ object GlobalDefinitions {
|
|||
}
|
||||
router.DrownAtMaxDepth = true
|
||||
router.MaxDepth = 2
|
||||
router.UnderwaterLifespan(suffocation = 45000L, recovery = 5000L) //but the router hovers over water, so ...?
|
||||
router.UnderwaterLifespan(suffocation = 45000L, recovery = 5000L) //but the router hovers over water, so ...?
|
||||
router.Geometry = GeometryForm.representByCylinder(radius = 3.64845f, height = 3.51563f) //TODO hexahedron
|
||||
router.collision.avatarCollisionDamageMax = 150 //it has to bonk you on the head when it falls?
|
||||
router.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 13), (0.5f, 35), (0.75f, 65), (1f, 90)))
|
||||
router.collision.z = CollisionZData(Array((6f, 1), (18f, 50), (30f, 150), (36f, 350), (39f, 900)))
|
||||
router.maxForwardSpeed = 60f
|
||||
router.mass = 60f
|
||||
|
||||
switchblade.Name = "switchblade"
|
||||
switchblade.MaxHealth = 1750
|
||||
|
|
@ -6585,6 +6716,11 @@ object GlobalDefinitions {
|
|||
recovery = 5000L
|
||||
) //but the switchblade hovers over water, so ...?
|
||||
switchblade.Geometry = GeometryForm.representByCylinder(radius = 2.4335f, height = 2.73438f)
|
||||
switchblade.collision.avatarCollisionDamageMax = 35
|
||||
switchblade.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 13), (0.5f, 35), (0.75f, 65), (1f, 90)))
|
||||
switchblade.collision.z = CollisionZData(Array((6f, 1), (18f, 50), (30f, 150), (36f, 350), (39f, 800)))
|
||||
switchblade.maxForwardSpeed = 80f
|
||||
switchblade.mass = 63.9f
|
||||
|
||||
flail.Name = "flail"
|
||||
flail.MaxHealth = 2400
|
||||
|
|
@ -6621,6 +6757,22 @@ object GlobalDefinitions {
|
|||
flail.MaxDepth = 2
|
||||
flail.UnderwaterLifespan(suffocation = 45000L, recovery = 5000L) //but the flail hovers over water, so ...?
|
||||
flail.Geometry = GeometryForm.representByCylinder(radius = 2.1875f, height = 2.21875f)
|
||||
flail.collision.avatarCollisionDamageMax = 175
|
||||
flail.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 12), (0.5f, 35), (0.75f, 65), (1f, 90)))
|
||||
flail.collision.z = CollisionZData(Array((6f, 1), (18f, 50), (30f, 150), (36f, 350), (39f, 900)))
|
||||
flail.maxForwardSpeed = 55f
|
||||
flail.mass = 73.5f
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize flight `VehicleDefinition` globals.
|
||||
*/
|
||||
private def init_flight_vehicles(): Unit = {
|
||||
val liberatorForm = GeometryForm.representByCylinder(radius = 3.74615f, height = 2.51563f) _
|
||||
val bailableSeat = new SeatDefinition() {
|
||||
bailable = true
|
||||
}
|
||||
val variantConverter = new VariantVehicleConverter
|
||||
|
||||
mosquito.Name = "mosquito"
|
||||
mosquito.MaxHealth = 665
|
||||
|
|
@ -6654,6 +6806,11 @@ object GlobalDefinitions {
|
|||
mosquito.DrownAtMaxDepth = true
|
||||
mosquito.MaxDepth = 2 //flying vehicles will automatically disable
|
||||
mosquito.Geometry = GeometryForm.representByCylinder(radius = 2.72108f, height = 2.5f)
|
||||
mosquito.collision.avatarCollisionDamageMax = 50
|
||||
mosquito.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 50), (0.5f, 100), (0.75f, 150), (1f, 200)))
|
||||
mosquito.collision.z = CollisionZData(Array((3f, 1), (9f, 25), (15f, 50), (18f, 75), (19.5f, 100)))
|
||||
mosquito.maxForwardSpeed = 120f
|
||||
mosquito.mass = 53.6f
|
||||
|
||||
lightgunship.Name = "lightgunship" // Reaver
|
||||
lightgunship.MaxHealth = 855 // Temporary - Correct Reaver Health from pre-"Coder Madness 2" Event
|
||||
|
|
@ -6688,6 +6845,11 @@ object GlobalDefinitions {
|
|||
lightgunship.DrownAtMaxDepth = true
|
||||
lightgunship.MaxDepth = 2 //flying vehicles will automatically disable
|
||||
lightgunship.Geometry = GeometryForm.representByCylinder(radius = 2.375f, height = 1.98438f)
|
||||
lightgunship.collision.avatarCollisionDamageMax = 750
|
||||
lightgunship.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 60), (0.5f, 120), (0.75f, 180), (1f, 250)))
|
||||
lightgunship.collision.z = CollisionZData(Array((3f, 1), (9f, 30), (15f, 60), (18f, 90), (19.5f, 125)))
|
||||
lightgunship.maxForwardSpeed = 104f
|
||||
lightgunship.mass = 51.1f
|
||||
|
||||
wasp.Name = "wasp"
|
||||
wasp.MaxHealth = 515
|
||||
|
|
@ -6721,6 +6883,11 @@ object GlobalDefinitions {
|
|||
wasp.DrownAtMaxDepth = true
|
||||
wasp.MaxDepth = 2 //flying vehicles will automatically disable
|
||||
wasp.Geometry = GeometryForm.representByCylinder(radius = 2.88675f, height = 2.5f)
|
||||
wasp.collision.avatarCollisionDamageMax = 50 //mosquito numbers
|
||||
wasp.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 50), (0.5f, 100), (0.75f, 150), (1f, 200))) //mosquito numbers
|
||||
wasp.collision.z = CollisionZData(Array((3f, 1), (9f, 25), (15f, 50), (18f, 75), (19.5f, 100))) //mosquito numbers
|
||||
wasp.maxForwardSpeed = 120f
|
||||
wasp.mass = 53.6f
|
||||
|
||||
liberator.Name = "liberator"
|
||||
liberator.MaxHealth = 2500
|
||||
|
|
@ -6729,7 +6896,7 @@ object GlobalDefinitions {
|
|||
liberator.RepairIfDestroyed = false
|
||||
liberator.MaxShields = 500
|
||||
liberator.CanFly = true
|
||||
liberator.Seats += 0 -> new SeatDefinition()
|
||||
liberator.Seats += 0 -> bailableSeat //new SeatDefinition()
|
||||
liberator.Seats += 1 -> bailableSeat
|
||||
liberator.Seats += 2 -> bailableSeat
|
||||
liberator.controlledWeapons += 0 -> 3
|
||||
|
|
@ -6763,6 +6930,11 @@ object GlobalDefinitions {
|
|||
liberator.DrownAtMaxDepth = true
|
||||
liberator.MaxDepth = 2 //flying vehicles will automatically disable
|
||||
liberator.Geometry = liberatorForm
|
||||
liberator.collision.avatarCollisionDamageMax = 100
|
||||
liberator.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 60), (0.5f, 120), (0.75f, 180), (1f, 250)))
|
||||
liberator.collision.z = CollisionZData(Array((3f, 1), (9f, 30), (15f, 60), (18f, 90), (19.5f, 125)))
|
||||
liberator.maxForwardSpeed = 90f
|
||||
liberator.mass = 82f
|
||||
|
||||
vulture.Name = "vulture"
|
||||
vulture.MaxHealth = 2500
|
||||
|
|
@ -6771,7 +6943,7 @@ object GlobalDefinitions {
|
|||
vulture.RepairIfDestroyed = false
|
||||
vulture.MaxShields = 500
|
||||
vulture.CanFly = true
|
||||
vulture.Seats += 0 -> new SeatDefinition()
|
||||
vulture.Seats += 0 -> bailableSeat //new SeatDefinition()
|
||||
vulture.Seats += 1 -> bailableSeat
|
||||
vulture.Seats += 2 -> bailableSeat
|
||||
vulture.controlledWeapons += 0 -> 3
|
||||
|
|
@ -6806,6 +6978,11 @@ object GlobalDefinitions {
|
|||
vulture.DrownAtMaxDepth = true
|
||||
vulture.MaxDepth = 2 //flying vehicles will automatically disable
|
||||
vulture.Geometry = liberatorForm
|
||||
vulture.collision.avatarCollisionDamageMax = 100
|
||||
vulture.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 60), (0.5f, 120), (0.75f, 180), (1f, 250)))
|
||||
vulture.collision.z = CollisionZData(Array((3f, 1), (9f, 30), (15f, 60), (18f, 90), (19.5f, 125)))
|
||||
vulture.maxForwardSpeed = 97f
|
||||
vulture.mass = 82f
|
||||
|
||||
dropship.Name = "dropship" // Galaxy
|
||||
dropship.MaxHealth = 5000
|
||||
|
|
@ -6815,7 +6992,7 @@ object GlobalDefinitions {
|
|||
dropship.RepairIfDestroyed = false
|
||||
dropship.MaxShields = 1000
|
||||
dropship.CanFly = true
|
||||
dropship.Seats += 0 -> new SeatDefinition()
|
||||
dropship.Seats += 0 -> bailableSeat //new SeatDefinition()
|
||||
dropship.Seats += 1 -> bailableSeat
|
||||
dropship.Seats += 2 -> bailableSeat
|
||||
dropship.Seats += 3 -> bailableSeat
|
||||
|
|
@ -6876,6 +7053,11 @@ object GlobalDefinitions {
|
|||
dropship.DrownAtMaxDepth = true
|
||||
dropship.MaxDepth = 2
|
||||
dropship.Geometry = GeometryForm.representByCylinder(radius = 10.52202f, height = 6.23438f)
|
||||
dropship.collision.avatarCollisionDamageMax = 300
|
||||
dropship.collision.xy = CollisionXYData(Array((0.1f, 5), (0.25f, 125), (0.5f, 250), (0.75f, 500), (1f, 1000)))
|
||||
dropship.collision.z = CollisionZData(Array((3f, 5), (9f, 125), (15f, 250), (18f, 500), (19.5f, 1000)))
|
||||
dropship.maxForwardSpeed = 80f
|
||||
dropship.mass = 133f
|
||||
|
||||
galaxy_gunship.Name = "galaxy_gunship"
|
||||
galaxy_gunship.MaxHealth = 6000
|
||||
|
|
@ -6885,7 +7067,7 @@ object GlobalDefinitions {
|
|||
galaxy_gunship.RepairIfDestroyed = false
|
||||
galaxy_gunship.MaxShields = 1200
|
||||
galaxy_gunship.CanFly = true
|
||||
galaxy_gunship.Seats += 0 -> new SeatDefinition()
|
||||
galaxy_gunship.Seats += 0 -> bailableSeat //new SeatDefinition()
|
||||
galaxy_gunship.Seats += 1 -> bailableSeat
|
||||
galaxy_gunship.Seats += 2 -> bailableSeat
|
||||
galaxy_gunship.Seats += 3 -> bailableSeat
|
||||
|
|
@ -6930,6 +7112,11 @@ object GlobalDefinitions {
|
|||
galaxy_gunship.DrownAtMaxDepth = true
|
||||
galaxy_gunship.MaxDepth = 2
|
||||
galaxy_gunship.Geometry = GeometryForm.representByCylinder(radius = 9.2382f, height = 5.01562f)
|
||||
galaxy_gunship.collision.avatarCollisionDamageMax = 300
|
||||
galaxy_gunship.collision.xy = CollisionXYData(Array((0.1f, 5), (0.25f, 125), (0.5f, 250), (0.75f, 500), (1f, 1000)))
|
||||
galaxy_gunship.collision.z = CollisionZData(Array((3f, 5), (9f, 125), (15f, 250), (18f, 500), (19.5f, 1000)))
|
||||
galaxy_gunship.maxForwardSpeed = 85f
|
||||
galaxy_gunship.mass = 133f
|
||||
|
||||
lodestar.Name = "lodestar"
|
||||
lodestar.MaxHealth = 5000
|
||||
|
|
@ -6939,7 +7126,7 @@ object GlobalDefinitions {
|
|||
lodestar.RepairIfDestroyed = false
|
||||
lodestar.MaxShields = 1000
|
||||
lodestar.CanFly = true
|
||||
lodestar.Seats += 0 -> new SeatDefinition()
|
||||
lodestar.Seats += 0 -> bailableSeat
|
||||
lodestar.MountPoints += 1 -> MountInfo(0)
|
||||
lodestar.MountPoints += 2 -> MountInfo(1)
|
||||
lodestar.Cargo += 1 -> new CargoDefinition()
|
||||
|
|
@ -6972,6 +7159,9 @@ object GlobalDefinitions {
|
|||
lodestar.DrownAtMaxDepth = true
|
||||
lodestar.MaxDepth = 2
|
||||
lodestar.Geometry = GeometryForm.representByCylinder(radius = 7.8671f, height = 6.79688f) //TODO hexahedron
|
||||
lodestar.collision.z = CollisionZData(Array((3f, 5), (9f, 125), (15f, 250), (18f, 500), (19.5f, 1000)))
|
||||
lodestar.maxForwardSpeed = 80f
|
||||
lodestar.mass = 128.2f
|
||||
|
||||
phantasm.Name = "phantasm"
|
||||
phantasm.MaxHealth = 2500
|
||||
|
|
@ -7011,6 +7201,11 @@ object GlobalDefinitions {
|
|||
phantasm.DrownAtMaxDepth = true
|
||||
phantasm.MaxDepth = 2
|
||||
phantasm.Geometry = GeometryForm.representByCylinder(radius = 5.2618f, height = 3f)
|
||||
phantasm.collision.avatarCollisionDamageMax = 100
|
||||
phantasm.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 60), (0.5f, 120), (0.75f, 180), (1f, 250)))
|
||||
phantasm.collision.z = CollisionZData(Array((3f, 1), (9f, 30), (15f, 60), (18f, 90), (19.5f, 125)))
|
||||
phantasm.maxForwardSpeed = 140f
|
||||
phantasm.mass = 100f
|
||||
|
||||
droppod.Name = "droppod"
|
||||
droppod.MaxHealth = 20000
|
||||
|
|
@ -7027,6 +7222,7 @@ object GlobalDefinitions {
|
|||
droppod.DestroyedModel = None //the adb calls out a droppod; the cyclic nature of this confounds me
|
||||
droppod.DamageUsing = DamageCalculations.AgainstAircraft
|
||||
droppod.DrownAtMaxDepth = false
|
||||
droppod.mass = 2500f
|
||||
|
||||
orbital_shuttle.Name = "orbital_shuttle"
|
||||
orbital_shuttle.MaxHealth = 20000
|
||||
|
|
@ -7059,6 +7255,7 @@ object GlobalDefinitions {
|
|||
orbital_shuttle.DestroyedModel = None
|
||||
orbital_shuttle.DamageUsing = DamageCalculations.AgainstNothing
|
||||
orbital_shuttle.DrownAtMaxDepth = false
|
||||
orbital_shuttle.mass = 25000f
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -7185,6 +7382,9 @@ object GlobalDefinitions {
|
|||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
spitfire_turret.Geometry = smallTurret
|
||||
spitfire_turret.collision.xy = CollisionXYData(Array((0.01f, 10), (0.02f, 40), (0.03f, 60), (0.04f, 80), (0.05f, 100)))
|
||||
spitfire_turret.collision.z = CollisionZData(Array((4f, 10), (4.25f, 40), (4.5f, 60), (4.75f, 80), (5f, 100)))
|
||||
spitfire_turret.mass = 5f
|
||||
|
||||
spitfire_cloaked.Name = "spitfire_cloaked"
|
||||
spitfire_cloaked.Descriptor = "CloakingSpitfires"
|
||||
|
|
@ -7209,6 +7409,9 @@ object GlobalDefinitions {
|
|||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
spitfire_cloaked.Geometry = smallTurret
|
||||
spitfire_cloaked.collision.xy = CollisionXYData(Array((0.01f, 10), (0.02f, 40), (0.03f, 60), (0.04f, 80), (0.05f, 100)))
|
||||
spitfire_cloaked.collision.z = CollisionZData(Array((4f, 10), (4.25f, 40), (4.5f, 60), (4.75f, 80), (5f, 100)))
|
||||
spitfire_cloaked.mass = 5f
|
||||
|
||||
spitfire_aa.Name = "spitfire_aa"
|
||||
spitfire_aa.Descriptor = "FlakSpitfires"
|
||||
|
|
@ -7233,6 +7436,9 @@ object GlobalDefinitions {
|
|||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
spitfire_aa.Geometry = smallTurret
|
||||
spitfire_aa.collision.xy = CollisionXYData(Array((0.01f, 10), (0.02f, 40), (0.03f, 60), (0.04f, 80), (0.05f, 100)))
|
||||
spitfire_aa.collision.z = CollisionZData(Array((4f, 10), (4.25f, 40), (4.5f, 60), (4.75f, 80), (5f, 100)))
|
||||
spitfire_aa.mass = 5f
|
||||
|
||||
motionalarmsensor.Name = "motionalarmsensor"
|
||||
motionalarmsensor.Descriptor = "MotionSensors"
|
||||
|
|
@ -7273,6 +7479,10 @@ object GlobalDefinitions {
|
|||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
tank_traps.Geometry = GeometryForm.representByCylinder(radius = 2.89680997f, height = 3.57812f)
|
||||
tank_traps.collision.xy = CollisionXYData(Array((0.01f, 5), (0.02f, 10), (0.03f, 15), (0.04f, 20), (0.05f, 25)))
|
||||
tank_traps.collision.z = CollisionZData(Array((4f, 10), (4.25f, 40), (4.5f, 60), (4.75f, 80), (5f, 100)))
|
||||
tank_traps.Modifiers = TrapCollisionDamageMultiplier(5f) //10f
|
||||
tank_traps.mass = 600f
|
||||
|
||||
val fieldTurretConverter = new FieldTurretConverter
|
||||
portable_manned_turret.Name = "portable_manned_turret"
|
||||
|
|
@ -7303,6 +7513,9 @@ object GlobalDefinitions {
|
|||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
portable_manned_turret.Geometry = largeTurret
|
||||
portable_manned_turret.collision.xy = CollisionXYData(Array((0.01f, 10), (0.02f, 40), (0.03f, 60), (0.04f, 80), (0.05f, 100)))
|
||||
portable_manned_turret.collision.z = CollisionZData(Array((4f, 10), (4.25f, 40), (4.5f, 60), (4.75f, 80), (5f, 100)))
|
||||
portable_manned_turret.mass = 100f
|
||||
|
||||
portable_manned_turret_nc.Name = "portable_manned_turret_nc"
|
||||
portable_manned_turret_nc.Descriptor = "FieldTurrets"
|
||||
|
|
@ -7332,6 +7545,9 @@ object GlobalDefinitions {
|
|||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
portable_manned_turret_nc.Geometry = largeTurret
|
||||
portable_manned_turret_nc.collision.xy = CollisionXYData(Array((0.01f, 10), (0.02f, 40), (0.03f, 60), (0.04f, 80), (0.05f, 100)))
|
||||
portable_manned_turret_nc.collision.z = CollisionZData(Array((4f, 10), (4.25f, 40), (4.5f, 60), (4.75f, 80), (5f, 100)))
|
||||
portable_manned_turret_nc.mass = 100f
|
||||
|
||||
portable_manned_turret_tr.Name = "portable_manned_turret_tr"
|
||||
portable_manned_turret_tr.Descriptor = "FieldTurrets"
|
||||
|
|
@ -7361,6 +7577,9 @@ object GlobalDefinitions {
|
|||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
portable_manned_turret_tr.Geometry = largeTurret
|
||||
portable_manned_turret_tr.collision.xy = CollisionXYData(Array((0.01f, 10), (0.02f, 40), (0.03f, 60), (0.04f, 80), (0.05f, 100)))
|
||||
portable_manned_turret_tr.collision.z = CollisionZData(Array((4f, 10), (4.25f, 40), (4.5f, 60), (4.75f, 80), (5f, 100)))
|
||||
portable_manned_turret_tr.mass = 100f
|
||||
|
||||
portable_manned_turret_vs.Name = "portable_manned_turret_vs"
|
||||
portable_manned_turret_vs.Descriptor = "FieldTurrets"
|
||||
|
|
@ -7390,6 +7609,9 @@ object GlobalDefinitions {
|
|||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
portable_manned_turret_vs.Geometry = largeTurret
|
||||
portable_manned_turret_vs.collision.xy = CollisionXYData(Array((0.01f, 10), (0.02f, 40), (0.03f, 60), (0.04f, 80), (0.05f, 100)))
|
||||
portable_manned_turret_vs.collision.z = CollisionZData(Array((4f, 10), (4.25f, 40), (4.5f, 60), (4.75f, 80), (5f, 100)))
|
||||
portable_manned_turret_vs.mass = 100f
|
||||
|
||||
deployable_shield_generator.Name = "deployable_shield_generator"
|
||||
deployable_shield_generator.Descriptor = "ShieldGenerators"
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import net.psforever.objects.serverobject.PlanetSideServerObject
|
|||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.serverobject.aura.AuraContainer
|
||||
import net.psforever.objects.serverobject.environment.InteractWithEnvironment
|
||||
import net.psforever.objects.serverobject.mount.MountableEntity
|
||||
import net.psforever.objects.vital.resistance.ResistanceProfile
|
||||
import net.psforever.objects.vital.Vitality
|
||||
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||
|
|
@ -31,9 +32,10 @@ class Player(var avatar: Avatar)
|
|||
with Container
|
||||
with JammableUnit
|
||||
with ZoneAware
|
||||
with AuraContainer {
|
||||
with AuraContainer
|
||||
with MountableEntity {
|
||||
interaction(new InteractWithEnvironment())
|
||||
interaction(new InteractWithMines(range = 10))
|
||||
interaction(new InteractWithMinesUnlessSpectating(obj = this, range = 10))
|
||||
|
||||
private var backpack: Boolean = false
|
||||
private var released: Boolean = false
|
||||
|
|
@ -594,3 +596,14 @@ object Player {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class InteractWithMinesUnlessSpectating(
|
||||
private val obj: Player,
|
||||
range: Float
|
||||
) extends InteractWithMines(range) {
|
||||
override def interaction(target: InteractsWithZone): Unit = {
|
||||
if (!obj.spectator) {
|
||||
super.interaction(target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,6 +101,8 @@ class TurretControl(turret: TurretDeployable)
|
|||
|
||||
override protected def DestructionAwareness(target: Target, cause: DamageResult): Unit = {
|
||||
super.DestructionAwareness(target, cause)
|
||||
CancelJammeredSound(target)
|
||||
CancelJammeredStatus(target)
|
||||
Deployables.AnnounceDestroyDeployable(turret, None)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import net.psforever.objects.ce.InteractWithMines
|
|||
import net.psforever.objects.definition.{ToolDefinition, VehicleDefinition}
|
||||
import net.psforever.objects.equipment.{EquipmentSize, EquipmentSlot, JammableUnit}
|
||||
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem, InventoryTile}
|
||||
import net.psforever.objects.serverobject.mount.{Seat, SeatDefinition}
|
||||
import net.psforever.objects.serverobject.mount.{MountableEntity, Seat, SeatDefinition}
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.serverobject.aura.AuraContainer
|
||||
|
|
@ -86,7 +86,8 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
|
|||
with JammableUnit
|
||||
with CommonNtuContainer
|
||||
with Container
|
||||
with AuraContainer {
|
||||
with AuraContainer
|
||||
with MountableEntity {
|
||||
interaction(new InteractWithEnvironment())
|
||||
interaction(new InteractWithMines(range = 30))
|
||||
|
||||
|
|
@ -110,11 +111,11 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
|
|||
private var utilities: Map[Int, Utility] = Map()
|
||||
private val trunk: GridInventory = GridInventory()
|
||||
|
||||
/**
|
||||
/*
|
||||
* Records the GUID of the cargo vehicle (galaxy/lodestar) this vehicle is stored in for DismountVehicleCargoMsg use
|
||||
* DismountVehicleCargoMsg only passes the player_guid and this vehicle's guid
|
||||
*/
|
||||
private var mountedIn: Option[PlanetSideGUID] = None
|
||||
//private var mountedIn: Option[PlanetSideGUID] = None
|
||||
|
||||
private var vehicleGatingManifest: Option[VehicleManifest] = None
|
||||
private var previousVehicleGatingManifest: Option[VehicleManifest] = None
|
||||
|
|
@ -142,22 +143,6 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
|
|||
/** How long it takes to jack the vehicle in seconds, based on the hacker's certification level */
|
||||
def JackingDuration: Array[Int] = Definition.JackingDuration
|
||||
|
||||
def MountedIn: Option[PlanetSideGUID] = {
|
||||
this.mountedIn
|
||||
}
|
||||
|
||||
def MountedIn_=(cargo_vehicle_guid: PlanetSideGUID): Option[PlanetSideGUID] = MountedIn_=(Some(cargo_vehicle_guid))
|
||||
|
||||
def MountedIn_=(cargo_vehicle_guid: Option[PlanetSideGUID]): Option[PlanetSideGUID] = {
|
||||
cargo_vehicle_guid match {
|
||||
case Some(_) =>
|
||||
this.mountedIn = cargo_vehicle_guid
|
||||
case None =>
|
||||
this.mountedIn = None
|
||||
}
|
||||
MountedIn
|
||||
}
|
||||
|
||||
override def Health_=(assignHealth: Int): Int = {
|
||||
//TODO should vehicle class enforce this?
|
||||
if (!Destroyed) {
|
||||
|
|
@ -499,6 +484,10 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
|
|||
|
||||
def DamageModel = Definition.asInstanceOf[DamageResistanceModel]
|
||||
|
||||
override def BailProtection_=(protect: Boolean): Boolean = {
|
||||
!Definition.CanFly && super.BailProtection_=(protect)
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the definition entry that is used to store and unload pertinent information about the `Vehicle`.
|
||||
* @return the vehicle's definition entry
|
||||
|
|
|
|||
|
|
@ -232,25 +232,13 @@ object Vehicles {
|
|||
log.info(s"${hacker.Name} has jacked a ${target.Definition.Name}")
|
||||
val zone = target.Zone
|
||||
// Forcefully dismount any cargo
|
||||
target.CargoHolds.values.foreach(cargoHold => {
|
||||
target.CargoHolds.foreach { case (index, cargoHold) =>
|
||||
cargoHold.occupant match {
|
||||
case Some(cargo: Vehicle) =>
|
||||
cargo.Seats(0).occupant match {
|
||||
case Some(_: Player) =>
|
||||
CargoBehavior.HandleVehicleCargoDismount(
|
||||
target.Zone,
|
||||
cargo.GUID,
|
||||
bailed = target.isFlying,
|
||||
requestedByPassenger = false,
|
||||
kicked = true
|
||||
)
|
||||
case _ =>
|
||||
log.error("FinishHackingVehicle: vehicle in cargo hold missing driver")
|
||||
CargoBehavior.HandleVehicleCargoDismount(cargo.GUID, cargo, target.GUID, target, bailed = false, requestedByPassenger = false, kicked = true)
|
||||
}
|
||||
cargo.Actor ! CargoBehavior.StartCargoDismounting(bailed = false)
|
||||
case None => ;
|
||||
}
|
||||
})
|
||||
}
|
||||
// Forcefully dismount all seated occupants from the vehicle
|
||||
target.Seats.values.foreach(seat => {
|
||||
seat.occupant match {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import net.psforever.objects.locker.LockerContainerControl
|
|||
import net.psforever.objects.serverobject.environment._
|
||||
import net.psforever.objects.serverobject.repair.Repairable
|
||||
import net.psforever.objects.serverobject.shuttle.OrbitalShuttlePad
|
||||
import net.psforever.objects.vital.collision.CollisionReason
|
||||
import net.psforever.objects.vital.environment.EnvironmentReason
|
||||
import net.psforever.objects.vital.etc.{PainboxReason, SuicideReason}
|
||||
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
||||
|
|
@ -416,7 +417,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
(afterHolsters ++ afterInventory).foreach { entry => entry.obj.Faction = player.Faction }
|
||||
afterHolsters.foreach {
|
||||
case InventoryItem(citem: ConstructionItem, _) =>
|
||||
Deployables.initializeConstructionAmmoMode(player.avatar.certifications, citem)
|
||||
Deployables.initializeConstructionItem(player.avatar.certifications, citem)
|
||||
case _ => ;
|
||||
}
|
||||
toDeleteOrDrop.foreach { entry => entry.obj.Faction = PlanetSideEmpire.NEUTRAL }
|
||||
|
|
@ -795,7 +796,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
case _ =>
|
||||
cause.interaction.cause.source.Aggravated.nonEmpty
|
||||
}
|
||||
//log historical event
|
||||
//log historical event (always)
|
||||
target.History(cause)
|
||||
//stat changes
|
||||
if (damageToCapacitor > 0) {
|
||||
|
|
@ -803,11 +804,11 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
target.Name,
|
||||
AvatarAction.PlanetsideAttributeSelf(targetGUID, 7, target.Capacitor.toLong)
|
||||
)
|
||||
announceConfrontation = true
|
||||
announceConfrontation = true //TODO should we?
|
||||
}
|
||||
if (damageToStamina > 0) {
|
||||
avatarActor ! AvatarActor.ConsumeStamina(damageToStamina)
|
||||
announceConfrontation = true
|
||||
announceConfrontation = true //TODO should we?
|
||||
}
|
||||
if (damageToHealth > 0) {
|
||||
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 0, health))
|
||||
|
|
@ -815,14 +816,19 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
}
|
||||
val countableDamage = damageToHealth + damageToArmor
|
||||
if(announceConfrontation) {
|
||||
if (!aggravated) {
|
||||
if (aggravated) {
|
||||
events ! AvatarServiceMessage(
|
||||
zoneId,
|
||||
AvatarAction.SendResponse(Service.defaultPlayerGUID, AggravatedDamageMessage(targetGUID, countableDamage))
|
||||
)
|
||||
} else {
|
||||
//activity on map
|
||||
zone.Activity ! Zone.HotSpot.Activity(cause)
|
||||
//alert to damage source
|
||||
cause.adversarial match {
|
||||
case Some(adversarial) =>
|
||||
adversarial.attacker match {
|
||||
case pSource : PlayerSource => //player damage
|
||||
case pSource: PlayerSource => //player damage
|
||||
val name = pSource.Name
|
||||
zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match {
|
||||
case Some(tplayer) =>
|
||||
|
|
@ -855,6 +861,11 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
target.Name,
|
||||
AvatarAction.EnvironmentalDamage(target.GUID, o.entity.GUID, countableDamage)
|
||||
)
|
||||
case _: CollisionReason =>
|
||||
events ! AvatarServiceMessage(
|
||||
zoneId,
|
||||
AvatarAction.SendResponse(Service.defaultPlayerGUID, AggravatedDamageMessage(targetGUID, countableDamage))
|
||||
)
|
||||
case _ =>
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
target.Name,
|
||||
|
|
@ -863,19 +874,6 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
//general alert
|
||||
zone.AvatarEvents ! AvatarServiceMessage(
|
||||
target.Name,
|
||||
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(countableDamage, Vector3.Zero))
|
||||
)
|
||||
}
|
||||
}
|
||||
if (aggravated) {
|
||||
events ! AvatarServiceMessage(
|
||||
zoneId,
|
||||
AvatarAction.SendResponse(Service.defaultPlayerGUID, AggravatedDamageMessage(targetGUID, countableDamage))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1137,7 +1135,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
//can not preserve ammo type in construction tool packets
|
||||
citem.resetAmmoTypes()
|
||||
}
|
||||
Deployables.initializeConstructionAmmoMode(player.avatar.certifications, citem)
|
||||
Deployables.initializeConstructionItem(player.avatar.certifications, citem)
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ final case class DeployableSource(
|
|||
faction: PlanetSideEmpire.Value,
|
||||
health: Int,
|
||||
shields: Int,
|
||||
ownerName: String,
|
||||
owner: SourceEntry,
|
||||
position: Vector3,
|
||||
orientation: Vector3
|
||||
) extends SourceEntry {
|
||||
|
|
@ -20,7 +20,7 @@ final case class DeployableSource(
|
|||
def Definition: ObjectDefinition with DeployableDefinition = obj_def
|
||||
def Health = health
|
||||
def Shields = shields
|
||||
def OwnerName = ownerName
|
||||
def OwnerName = owner.Name
|
||||
def Position = position
|
||||
def Orientation = orientation
|
||||
def Velocity = None
|
||||
|
|
@ -29,12 +29,19 @@ final case class DeployableSource(
|
|||
|
||||
object DeployableSource {
|
||||
def apply(obj: Deployable): DeployableSource = {
|
||||
val ownerName = obj.OwnerName
|
||||
val ownerSource = (obj.Zone.LivePlayers ++ obj.Zone.Corpses)
|
||||
.find { p => ownerName.contains(p.Name) }
|
||||
match {
|
||||
case Some(p) => SourceEntry(p)
|
||||
case _ => SourceEntry.None
|
||||
}
|
||||
DeployableSource(
|
||||
obj.Definition,
|
||||
obj.Faction,
|
||||
obj.Health,
|
||||
obj.Shields,
|
||||
obj.OwnerName.getOrElse(""),
|
||||
ownerSource,
|
||||
obj.Position,
|
||||
obj.Orientation
|
||||
)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ final case class VehicleSource(
|
|||
position: Vector3,
|
||||
orientation: Vector3,
|
||||
velocity: Option[Vector3],
|
||||
occupants: List[SourceEntry],
|
||||
modifiers: ResistanceProfile
|
||||
) extends SourceEntry {
|
||||
override def Name = SourceEntry.NameFormat(obj_def.Name)
|
||||
|
|
@ -37,6 +38,12 @@ object VehicleSource {
|
|||
obj.Position,
|
||||
obj.Orientation,
|
||||
obj.Velocity,
|
||||
obj.Seats.values.map { seat =>
|
||||
seat.occupant match {
|
||||
case Some(p) => PlayerSource(p)
|
||||
case _ => SourceEntry.None
|
||||
}
|
||||
}.toList,
|
||||
obj.Definition.asInstanceOf[ResistanceProfile]
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import net.psforever.objects.definition.converter.SmallDeployableConverter
|
|||
import net.psforever.objects.vital.damage.DamageCalculations
|
||||
import net.psforever.objects.vital.resistance.ResistanceProfileMutators
|
||||
import net.psforever.objects.vital.resolution.DamageResistanceModel
|
||||
import net.psforever.objects.vital.{NoResistanceSelection, VitalityDefinition}
|
||||
import net.psforever.objects.vital.{CollisionXYData, NoResistanceSelection, VitalityDefinition}
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
|
|
@ -60,6 +60,7 @@ abstract class DeployableDefinition(objectId: Int)
|
|||
ResistUsing = NoResistanceSelection
|
||||
Packet = new SmallDeployableConverter
|
||||
registerAs = "deployables"
|
||||
collision.xy = new CollisionXYData(List((0f, 100)))
|
||||
|
||||
def Item: DeployedItem.Value = item
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ class ExoSuitDefinition(private val suitType: ExoSuitType.Value)
|
|||
protected var capacitorRechargeDelayMillis: Int = 0
|
||||
protected var capacitorRechargePerSecond: Int = 0
|
||||
protected var capacitorDrainPerSecond: Int = 0
|
||||
val collision: ExosuitCollisionData = new ExosuitCollisionData()
|
||||
Name = "exo-suit"
|
||||
DamageUsing = DamageCalculations.AgainstExoSuit
|
||||
ResistUsing = StandardInfantryResistance
|
||||
|
|
|
|||
|
|
@ -101,5 +101,15 @@ abstract class ObjectDefinition(private val objectId: Int) extends BasicDefiniti
|
|||
Geometry
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum forward speed that can be expected to be achieved by this unit.
|
||||
* Faster speeds are not discounted due to conditions of the motion or game environment
|
||||
* but speeds too far beyond this measure should be considered suspicious.
|
||||
* For ground vehicles, this field is called `maxForward` in the ADB.
|
||||
* For flight vehicles, this field is called `MaxSpeed` and `flightmaxspeed` in the ADB,
|
||||
* and it does not factor in the afterburner.
|
||||
*/
|
||||
var maxForwardSpeed: Float = 0f
|
||||
|
||||
def ObjectId: Int = objectId
|
||||
}
|
||||
|
|
|
|||
|
|
@ -197,6 +197,8 @@ class VehicleDefinition(objectId: Int)
|
|||
obj.Actor ! akka.actor.PoisonPill
|
||||
obj.Actor = Default.Actor
|
||||
}
|
||||
|
||||
override val collision: AdvancedCollisionData = new AdvancedCollisionData()
|
||||
}
|
||||
|
||||
object VehicleDefinition {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import akka.actor.Actor
|
|||
import net.psforever.objects.{Vehicle, Vehicles}
|
||||
import net.psforever.objects.equipment.JammableUnit
|
||||
import net.psforever.objects.serverobject.damage.Damageable.Target
|
||||
import net.psforever.objects.vital.base.DamageResolution
|
||||
import net.psforever.objects.vital.interaction.DamageResult
|
||||
import net.psforever.objects.vital.resolution.ResolutionCalculations
|
||||
import net.psforever.services.Service
|
||||
|
|
@ -103,13 +104,13 @@ trait DamageableVehicle
|
|||
case _ => (0, 0, 0)
|
||||
}
|
||||
var announceConfrontation: Boolean = reportDamageToVehicle || totalDamage > 0
|
||||
val aggravated = TryAggravationEffectActivate(cause) match {
|
||||
val showAsAggravated = (TryAggravationEffectActivate(cause) match {
|
||||
case Some(_) =>
|
||||
announceConfrontation = true
|
||||
false
|
||||
case _ =>
|
||||
cause.interaction.cause.source.Aggravated.nonEmpty
|
||||
}
|
||||
}) || cause.interaction.cause.resolution == DamageResolution.Collision
|
||||
reportDamageToVehicle = false
|
||||
|
||||
if (obj.MountedIn.nonEmpty) {
|
||||
|
|
@ -139,7 +140,7 @@ trait DamageableVehicle
|
|||
}
|
||||
}
|
||||
if (announceConfrontation) {
|
||||
if (aggravated) {
|
||||
if (showAsAggravated) {
|
||||
val msg = VehicleAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(totalDamage, Vector3.Zero))
|
||||
obj.Seats.values
|
||||
.collect { case seat if seat.occupant.nonEmpty => seat.occupant.get.Name }
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package net.psforever.objects.serverobject.mount
|
|||
|
||||
import akka.actor.ActorRef
|
||||
import net.psforever.objects.Player
|
||||
import net.psforever.types.BailType
|
||||
|
||||
import scala.annotation.tailrec
|
||||
|
||||
|
|
@ -97,7 +98,11 @@ object Mountable {
|
|||
* @param player the player who sent this request message
|
||||
* @param seat_num the seat index
|
||||
*/
|
||||
final case class TryDismount(player: Player, seat_num: Int)
|
||||
final case class TryDismount(player: Player, seat_num: Int, bailType: BailType.Value)
|
||||
|
||||
object TryDismount {
|
||||
def apply(player: Player, seatNum: Int): TryDismount = TryDismount(player, seatNum, BailType.Normal)
|
||||
}
|
||||
|
||||
/**
|
||||
* A basic `Trait` connecting all of the actionable `Mountable` response messages.
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import net.psforever.objects.Player
|
|||
import net.psforever.objects.entity.WorldEntity
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.hackable.Hackable
|
||||
import net.psforever.types.BailType
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
|
|
@ -82,9 +83,9 @@ trait MountableBehavior {
|
|||
* @see `Mountable`
|
||||
*/
|
||||
val dismountBehavior: Receive = {
|
||||
case Mountable.TryDismount(user, seat_number) =>
|
||||
case Mountable.TryDismount(user, seat_number, bail_type) =>
|
||||
val obj = MountableObject
|
||||
if (dismountTest(obj, seat_number, user) && tryDismount(obj, seat_number, user)) {
|
||||
if (dismountTest(obj, seat_number, user) && tryDismount(obj, seat_number, user, bail_type)) {
|
||||
user.VehicleSeated = None
|
||||
obj.Zone.actor ! ZoneActor.AddToBlockMap(user, obj.Position)
|
||||
sender() ! Mountable.MountMessages(
|
||||
|
|
@ -112,11 +113,12 @@ trait MountableBehavior {
|
|||
private def tryDismount(
|
||||
obj: Mountable,
|
||||
seatNumber: Int,
|
||||
user: Player
|
||||
user: Player,
|
||||
bailType: BailType.Value
|
||||
): Boolean = {
|
||||
obj.Seats.get(seatNumber) match {
|
||||
case Some(seat) => seat.unmount(user).isEmpty
|
||||
case _ => false
|
||||
case Some(seat) => seat.unmount(user, bailType).isEmpty
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
package net.psforever.objects.serverobject.mount
|
||||
|
||||
trait MountableSpace[A] {
|
||||
import net.psforever.types.BailType
|
||||
|
||||
trait MountableSpace[A <: MountableEntity] {
|
||||
private var _occupant: Option[A] = None
|
||||
|
||||
/**
|
||||
|
|
@ -53,6 +55,7 @@ trait MountableSpace[A] {
|
|||
target match {
|
||||
case Some(p) if testToMount(p) =>
|
||||
_occupant = target
|
||||
p.BailProtection = false
|
||||
target
|
||||
case _ =>
|
||||
occupant
|
||||
|
|
@ -63,7 +66,7 @@ trait MountableSpace[A] {
|
|||
* Tests whether the target is allowed to be mounted.
|
||||
* @see `MountableSpace[A].canBeOccupiedBy(A)`
|
||||
*/
|
||||
protected def testToMount(target: A): Boolean = canBeOccupied && canBeOccupiedBy(target)
|
||||
protected def testToMount(target: A): Boolean = target.MountedIn.isEmpty && canBeOccupied && canBeOccupiedBy(target)
|
||||
|
||||
/**
|
||||
* Attempt to dismount the target entity from this space.
|
||||
|
|
@ -73,10 +76,22 @@ trait MountableSpace[A] {
|
|||
/**
|
||||
* Attempt to dismount the target entity from this space.
|
||||
*/
|
||||
def unmount(target: Option[A]): Option[A] = {
|
||||
def unmount(target: A, bailType: BailType.Value): Option[A] = unmount(Some(target), bailType)
|
||||
|
||||
/**
|
||||
* Attempt to dismount the target entity from this space.
|
||||
*/
|
||||
def unmount(target: Option[A]): Option[A] = unmount(target, BailType.Normal)
|
||||
|
||||
/**
|
||||
* Attempt to dismount the target entity from this space.
|
||||
* @return the current seat occupant, which should be `None` if the operation was successful
|
||||
*/
|
||||
def unmount(target: Option[A], bailType: BailType.Value): Option[A] = {
|
||||
target match {
|
||||
case Some(p) if testToUnmount(p) =>
|
||||
_occupant = None
|
||||
p.BailProtection = bailable && (bailType == BailType.Bailed || bailType == BailType.Kicked)
|
||||
None
|
||||
case _ =>
|
||||
occupant
|
||||
|
|
|
|||
|
|
@ -5,7 +5,5 @@ import net.psforever.objects.Vehicle
|
|||
import net.psforever.objects.serverobject.mount.{MountableSpace, MountableSpaceDefinition}
|
||||
|
||||
class Cargo(private val cdef: MountableSpaceDefinition[Vehicle]) extends MountableSpace[Vehicle] {
|
||||
override protected def testToMount(target: Vehicle): Boolean = target.MountedIn.isEmpty && super.testToMount(target)
|
||||
|
||||
def definition: MountableSpaceDefinition[Vehicle] = cdef
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,12 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.vehicles
|
||||
|
||||
import akka.actor.{Actor, Cancellable}
|
||||
import net.psforever.actors.zone.ZoneActor
|
||||
import net.psforever.objects.zones.Zone
|
||||
import akka.actor.Actor
|
||||
import net.psforever.objects._
|
||||
import net.psforever.objects.vehicles.CargoBehavior.{CheckCargoDismount, CheckCargoMounting}
|
||||
import net.psforever.packet.game.{CargoMountPointStatusMessage, ObjectAttachMessage, ObjectDetachMessage, PlanetsideAttributeMessage}
|
||||
import net.psforever.types.{CargoStatus, PlanetSideGUID, Vector3}
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import net.psforever.types.PlanetSideGUID
|
||||
|
||||
trait CargoBehavior {
|
||||
_: Actor =>
|
||||
private var cargoMountTimer: Cancellable = Default.Cancellable
|
||||
private var cargoDismountTimer: Cancellable = Default.Cancellable
|
||||
|
||||
/* gate-keep mounting behavior so that unit does not try to dismount as cargo, or mount different vehicle */
|
||||
private var isMounting: Option[PlanetSideGUID] = None
|
||||
/* gate-keep dismounting behavior so that unit does not try to mount as cargo, or dismount from different vehicle */
|
||||
|
|
@ -26,547 +14,79 @@ trait CargoBehavior {
|
|||
|
||||
def CargoObject: Vehicle
|
||||
|
||||
def endAllCargoOperations(): Unit = {
|
||||
val obj = CargoObject
|
||||
val zone = obj.Zone
|
||||
zone.GUID(isMounting) match {
|
||||
case Some(v : Vehicle) => v.Actor ! CargoBehavior.EndCargoMounting(obj.GUID)
|
||||
case _ => ;
|
||||
}
|
||||
isMounting = None
|
||||
zone.GUID(isDismounting) match {
|
||||
case Some(v: Vehicle) => v.Actor ! CargoBehavior.EndCargoDismounting(obj.GUID)
|
||||
case _ => ;
|
||||
}
|
||||
isDismounting = None
|
||||
startCargoDismounting(bailed = false)
|
||||
}
|
||||
|
||||
val cargoBehavior: Receive = {
|
||||
case CheckCargoMounting(carrier_guid, mountPoint, iteration) =>
|
||||
val obj = CargoObject
|
||||
if (
|
||||
(isMounting.isEmpty || isMounting.contains(carrier_guid)) && isDismounting.isEmpty &&
|
||||
CargoBehavior.HandleCheckCargoMounting(obj.Zone, carrier_guid, obj.GUID, obj, mountPoint, iteration)
|
||||
) {
|
||||
if (iteration == 0) {
|
||||
//open the cargo bay door
|
||||
obj.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
obj.Zone.id,
|
||||
AvatarAction.SendResponse(
|
||||
Service.defaultPlayerGUID,
|
||||
CargoMountPointStatusMessage(
|
||||
carrier_guid,
|
||||
PlanetSideGUID(0),
|
||||
obj.GUID,
|
||||
PlanetSideGUID(0),
|
||||
mountPoint,
|
||||
CargoStatus.InProgress,
|
||||
0
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
isMounting = Some(carrier_guid)
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
cargoMountTimer.cancel()
|
||||
cargoMountTimer = context.system.scheduler.scheduleOnce(
|
||||
250 milliseconds,
|
||||
self,
|
||||
CheckCargoMounting(carrier_guid, mountPoint, iteration + 1)
|
||||
)
|
||||
} else {
|
||||
case CargoBehavior.StartCargoMounting(carrier_guid, mountPoint) =>
|
||||
startCargoMounting(carrier_guid, mountPoint)
|
||||
|
||||
case CargoBehavior.StartCargoDismounting(bailed) =>
|
||||
startCargoDismounting(bailed)
|
||||
|
||||
case CargoBehavior.EndCargoMounting(carrier_guid) =>
|
||||
if (isMounting.contains(carrier_guid)) {
|
||||
isMounting = None
|
||||
}
|
||||
|
||||
case CheckCargoDismount(carrier_guid, mountPoint, iteration) =>
|
||||
val obj = CargoObject
|
||||
if (
|
||||
(isDismounting.isEmpty || isDismounting.contains(carrier_guid)) && isMounting.isEmpty &&
|
||||
CargoBehavior.HandleCheckCargoDismounting(obj.Zone, carrier_guid, obj.GUID, obj, mountPoint, iteration)
|
||||
) {
|
||||
isDismounting = Some(carrier_guid)
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
cargoDismountTimer.cancel()
|
||||
cargoDismountTimer = context.system.scheduler.scheduleOnce(
|
||||
250 milliseconds,
|
||||
self,
|
||||
CheckCargoDismount(carrier_guid, mountPoint, iteration + 1)
|
||||
)
|
||||
} else {
|
||||
case CargoBehavior.EndCargoDismounting(carrier_guid) =>
|
||||
if (isDismounting.contains(carrier_guid)) {
|
||||
isDismounting = None
|
||||
}
|
||||
}
|
||||
|
||||
def startCargoMounting(carrier_guid: PlanetSideGUID, mountPoint: Int): Unit = {
|
||||
val obj = CargoObject
|
||||
obj.Zone.GUID(carrier_guid) match {
|
||||
case Some(carrier: Vehicle)
|
||||
if isMounting.isEmpty && isDismounting.isEmpty && (carrier.CargoHolds.get(mountPoint) match {
|
||||
case Some(hold) => !hold.isOccupied
|
||||
case _ => false
|
||||
}) =>
|
||||
isMounting = Some(carrier_guid)
|
||||
carrier.Actor ! CarrierBehavior.CheckCargoMounting(obj.GUID, mountPoint, 0)
|
||||
case _ => ;
|
||||
isMounting = None
|
||||
}
|
||||
}
|
||||
|
||||
def startCargoDismounting(bailed: Boolean): Unit = {
|
||||
val obj = CargoObject
|
||||
obj.Zone.GUID(obj.MountedIn) match {
|
||||
case Some(carrier: Vehicle) =>
|
||||
carrier.CargoHolds.find { case (_, hold) => hold.occupant.contains(obj) } match {
|
||||
case Some((mountPoint, _))
|
||||
if isDismounting.isEmpty && isMounting.isEmpty =>
|
||||
isDismounting = obj.MountedIn
|
||||
carrier.Actor ! CarrierBehavior.CheckCargoDismount(obj.GUID, mountPoint, 0, bailed)
|
||||
|
||||
case _ =>
|
||||
obj.MountedIn = None
|
||||
isDismounting = None
|
||||
}
|
||||
case _ =>
|
||||
obj.MountedIn = None
|
||||
isDismounting = None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object CargoBehavior {
|
||||
private val log = org.log4s.getLogger("CargoBehavior")
|
||||
|
||||
final case class CheckCargoMounting(carrier_guid: PlanetSideGUID, cargo_mountpoint: Int, iteration: Int)
|
||||
final case class CheckCargoDismount(carrier_guid: PlanetSideGUID, cargo_mountpoint: Int, iteration: Int)
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param carrierGUID the ferrying carrier vehicle
|
||||
* @param cargoGUID the vehicle being ferried as cargo
|
||||
* @param cargo the vehicle being ferried as cargo
|
||||
* @param mountPoint the cargo hold to which the cargo vehicle is stowed
|
||||
* @param iteration number of times a proper mounting for this combination has been queried
|
||||
*/
|
||||
def HandleCheckCargoMounting(
|
||||
zone: Zone,
|
||||
carrierGUID: PlanetSideGUID,
|
||||
cargoGUID: PlanetSideGUID,
|
||||
cargo: Vehicle,
|
||||
mountPoint: Int,
|
||||
iteration: Int
|
||||
): Boolean = {
|
||||
zone.GUID(carrierGUID) match {
|
||||
case Some(carrier: Vehicle) =>
|
||||
HandleCheckCargoMounting(cargoGUID, cargo, carrierGUID, carrier, mountPoint, iteration)
|
||||
case carrier if iteration > 0 =>
|
||||
log.warn(s"HandleCheckCargoMounting: participant vehicles changed in the middle of a mounting event")
|
||||
LogCargoEventMissingVehicleError("HandleCheckCargoMounting: carrier", carrier, carrierGUID)
|
||||
false
|
||||
case _ =>
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param cargoGUID the vehicle being ferried as cargo
|
||||
* @param cargo the vehicle being ferried as cargo
|
||||
* @param carrierGUID the ferrying carrier vehicle
|
||||
* @param carrier the ferrying carrier vehicle
|
||||
* @param mountPoint the cargo hold to which the cargo vehicle is stowed
|
||||
* @param iteration number of times a proper mounting for this combination has been queried
|
||||
*/
|
||||
private def HandleCheckCargoMounting(
|
||||
cargoGUID: PlanetSideGUID,
|
||||
cargo: Vehicle,
|
||||
carrierGUID: PlanetSideGUID,
|
||||
carrier: Vehicle,
|
||||
mountPoint: Int,
|
||||
iteration: Int
|
||||
): Boolean = {
|
||||
val zone = carrier.Zone
|
||||
val distance = Vector3.DistanceSquared(cargo.Position, carrier.Position)
|
||||
carrier.CargoHold(mountPoint) match {
|
||||
case Some(hold) if !hold.isOccupied =>
|
||||
log.debug(
|
||||
s"HandleCheckCargoMounting: mount distance between $cargoGUID and $carrierGUID - actual=$distance, target=64"
|
||||
)
|
||||
if (distance <= 64) {
|
||||
//cargo vehicle is close enough to assume to be physically within the carrier's hold; mount it
|
||||
log.debug(s"HandleCheckCargoMounting: mounting cargo vehicle in carrier at distance of $distance")
|
||||
hold.mount(cargo)
|
||||
cargo.MountedIn = carrierGUID
|
||||
cargo.Velocity = None
|
||||
zone.VehicleEvents ! VehicleServiceMessage(
|
||||
s"${cargo.Actor}",
|
||||
VehicleAction.SendResponse(PlanetSideGUID(0), PlanetsideAttributeMessage(cargoGUID, 0, cargo.Health))
|
||||
)
|
||||
zone.VehicleEvents ! VehicleServiceMessage(
|
||||
s"${cargo.Actor}",
|
||||
VehicleAction.SendResponse(PlanetSideGUID(0), PlanetsideAttributeMessage(cargoGUID, 68, cargo.Shields))
|
||||
)
|
||||
CargoMountBehaviorForAll(carrier, cargo, mountPoint)
|
||||
zone.actor ! ZoneActor.RemoveFromBlockMap(cargo)
|
||||
false
|
||||
} else if (distance > 625 || iteration >= 40) {
|
||||
//vehicles moved too far away or took too long to get into proper position; abort mounting
|
||||
log.debug(
|
||||
"HandleCheckCargoMounting: cargo vehicle is too far away or didn't mount within allocated time - aborting"
|
||||
)
|
||||
val cargoDriverGUID = cargo.Seats(0).occupant.get.GUID
|
||||
zone.VehicleEvents ! VehicleServiceMessage(
|
||||
zone.id,
|
||||
VehicleAction.SendResponse(
|
||||
cargoDriverGUID,
|
||||
CargoMountPointStatusMessage(
|
||||
carrierGUID,
|
||||
PlanetSideGUID(0),
|
||||
PlanetSideGUID(0),
|
||||
cargoGUID,
|
||||
mountPoint,
|
||||
CargoStatus.Empty,
|
||||
0
|
||||
)
|
||||
)
|
||||
)
|
||||
false
|
||||
//sending packet to the cargo vehicle's client results in player being lock in own vehicle
|
||||
//player gets stuck as "always trying to remount the cargo hold"
|
||||
//obviously, don't do this
|
||||
} else {
|
||||
//cargo vehicle still not in position but there is more time to wait; reschedule check
|
||||
true
|
||||
}
|
||||
case None =>
|
||||
;
|
||||
log.warn(s"HandleCheckCargoMounting: carrier vehicle $carrier does not have a cargo hold #$mountPoint")
|
||||
false
|
||||
case _ =>
|
||||
if (iteration == 0) {
|
||||
log.warn(
|
||||
s"HandleCheckCargoMounting: carrier vehicle $carrier already possesses cargo in hold #$mountPoint; this operation was initiated incorrectly"
|
||||
)
|
||||
} else {
|
||||
log.error(
|
||||
s"HandleCheckCargoMounting: something has attached to the carrier vehicle $carrier cargo of hold #$mountPoint while a cargo dismount event was ongoing; stopped at iteration $iteration / 40"
|
||||
)
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param cargoGUID na
|
||||
* @param carrierGUID na
|
||||
* @param mountPoint na
|
||||
* @param iteration na
|
||||
*/
|
||||
def HandleCheckCargoDismounting(
|
||||
zone: Zone,
|
||||
carrierGUID: PlanetSideGUID,
|
||||
cargoGUID: PlanetSideGUID,
|
||||
cargo: Vehicle,
|
||||
mountPoint: Int,
|
||||
iteration: Int
|
||||
): Boolean = {
|
||||
zone.GUID(carrierGUID) match {
|
||||
case Some(carrier: Vehicle) =>
|
||||
HandleCheckCargoDismounting(cargoGUID, cargo, carrierGUID, carrier, mountPoint, iteration)
|
||||
case carrier if iteration > 0 =>
|
||||
log.error(s"HandleCheckCargoDismounting: participant vehicles changed in the middle of a mounting event")
|
||||
LogCargoEventMissingVehicleError("HandleCheckCargoDismounting: carrier", carrier, carrierGUID)
|
||||
false
|
||||
case _ =>
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param cargoGUID na
|
||||
* @param cargo na
|
||||
* @param carrierGUID na
|
||||
* @param carrier na
|
||||
* @param mountPoint na
|
||||
* @param iteration na
|
||||
*/
|
||||
private def HandleCheckCargoDismounting(
|
||||
cargoGUID: PlanetSideGUID,
|
||||
cargo: Vehicle,
|
||||
carrierGUID: PlanetSideGUID,
|
||||
carrier: Vehicle,
|
||||
mountPoint: Int,
|
||||
iteration: Int
|
||||
): Boolean = {
|
||||
val zone = carrier.Zone
|
||||
carrier.CargoHold(mountPoint) match {
|
||||
case Some(hold) if !hold.isOccupied =>
|
||||
val distance = Vector3.DistanceSquared(cargo.Position, carrier.Position)
|
||||
log.debug(
|
||||
s"HandleCheckCargoDismounting: mount distance between $cargoGUID and $carrierGUID - actual=$distance, target=225"
|
||||
)
|
||||
if (distance > 225) {
|
||||
//cargo vehicle has moved far enough away; close the carrier's hold door
|
||||
log.debug(
|
||||
s"HandleCheckCargoDismounting: dismount of cargo vehicle from carrier complete at distance of $distance"
|
||||
)
|
||||
val cargoDriverGUID = cargo.Seats(0).occupant.get.GUID
|
||||
zone.VehicleEvents ! VehicleServiceMessage(
|
||||
zone.id,
|
||||
VehicleAction.SendResponse(
|
||||
cargoDriverGUID,
|
||||
CargoMountPointStatusMessage(
|
||||
carrierGUID,
|
||||
PlanetSideGUID(0),
|
||||
PlanetSideGUID(0),
|
||||
cargoGUID,
|
||||
mountPoint,
|
||||
CargoStatus.Empty,
|
||||
0
|
||||
)
|
||||
)
|
||||
)
|
||||
false
|
||||
//sending packet to the cargo vehicle's client results in player being lock in own vehicle
|
||||
//player gets stuck as "always trying to remount the cargo hold"
|
||||
//obviously, don't do this
|
||||
} else if (iteration > 40) {
|
||||
//cargo vehicle has spent too long not getting far enough away; restore the cargo's mount in the carrier hold
|
||||
hold.mount(cargo)
|
||||
cargo.MountedIn = carrierGUID
|
||||
CargoMountBehaviorForAll(carrier, cargo, mountPoint)
|
||||
zone.actor ! ZoneActor.RemoveFromBlockMap(cargo)
|
||||
false
|
||||
} else {
|
||||
//cargo vehicle did not move far away enough yet and there is more time to wait; reschedule check
|
||||
true
|
||||
}
|
||||
case None =>
|
||||
log.warn(s"HandleCheckCargoDismounting: carrier vehicle $carrier does not have a cargo hold #$mountPoint")
|
||||
false
|
||||
case _ =>
|
||||
if (iteration == 0) {
|
||||
log.warn(
|
||||
s"HandleCheckCargoDismounting: carrier vehicle $carrier will not discharge the cargo of hold #$mountPoint; this operation was initiated incorrectly"
|
||||
)
|
||||
} else {
|
||||
log.error(
|
||||
s"HandleCheckCargoDismounting: something has attached to the carrier vehicle $carrier cargo of hold #$mountPoint while a cargo dismount event was ongoing; stopped at iteration $iteration / 40"
|
||||
)
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param zone na
|
||||
* @param cargo_guid na
|
||||
* @param bailed na
|
||||
* @param requestedByPassenger na
|
||||
* @param kicked na
|
||||
*/
|
||||
def HandleVehicleCargoDismount(
|
||||
zone: Zone,
|
||||
cargo_guid: PlanetSideGUID,
|
||||
bailed: Boolean,
|
||||
requestedByPassenger: Boolean,
|
||||
kicked: Boolean
|
||||
): Unit = {
|
||||
zone.GUID(cargo_guid) match {
|
||||
case Some(cargo: Vehicle) =>
|
||||
zone.GUID(cargo.MountedIn) match {
|
||||
case Some(ferry: Vehicle) =>
|
||||
HandleVehicleCargoDismount(cargo_guid, cargo, ferry.GUID, ferry, bailed, requestedByPassenger, kicked)
|
||||
case _ =>
|
||||
log.warn(
|
||||
s"DismountVehicleCargo: target ${cargo.Definition.Name}@$cargo_guid does not know what treats it as cargo"
|
||||
)
|
||||
}
|
||||
case _ =>
|
||||
log.warn(s"DismountVehicleCargo: target $cargo_guid either is not a vehicle in ${zone.id} or does not exist")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param cargoGUID the globally unique number for the vehicle being ferried
|
||||
* @param cargo the vehicle being ferried
|
||||
* @param carrierGUID the globally unique number for the vehicle doing the ferrying
|
||||
* @param carrier the vehicle doing the ferrying
|
||||
* @param bailed the ferried vehicle is bailing from the cargo hold
|
||||
* @param requestedByPassenger the ferried vehicle is being politely disembarked from the cargo hold
|
||||
* @param kicked the ferried vehicle is being kicked out of the cargo hold
|
||||
*/
|
||||
def HandleVehicleCargoDismount(
|
||||
cargoGUID: PlanetSideGUID,
|
||||
cargo: Vehicle,
|
||||
carrierGUID: PlanetSideGUID,
|
||||
carrier: Vehicle,
|
||||
bailed: Boolean,
|
||||
requestedByPassenger: Boolean,
|
||||
kicked: Boolean
|
||||
): Unit = {
|
||||
val zone = carrier.Zone
|
||||
carrier.CargoHolds.find({ case (_, hold) => hold.occupant.contains(cargo) }) match {
|
||||
case Some((mountPoint, hold)) =>
|
||||
cargo.MountedIn = None
|
||||
hold.unmount(cargo)
|
||||
val driverOpt = cargo.Seats(0).occupant
|
||||
val rotation: Vector3 = if (Vehicles.CargoOrientation(cargo) == 1) { //TODO: BFRs will likely also need this set
|
||||
//dismount router "sideways" in a lodestar
|
||||
carrier.Orientation.xy + Vector3.z((carrier.Orientation.z - 90) % 360)
|
||||
} else {
|
||||
carrier.Orientation
|
||||
}
|
||||
val cargoHoldPosition: Vector3 = if (carrier.Definition == GlobalDefinitions.dropship) {
|
||||
//the galaxy cargo bay is offset backwards from the center of the vehicle
|
||||
carrier.Position + Vector3.Rz(Vector3(0, 7, 0), math.toRadians(carrier.Orientation.z))
|
||||
} else {
|
||||
//the lodestar's cargo hold is almost the center of the vehicle
|
||||
carrier.Position
|
||||
}
|
||||
val GUID0 = Service.defaultPlayerGUID
|
||||
val zoneId = zone.id
|
||||
val events = zone.VehicleEvents
|
||||
val cargoActor = cargo.Actor
|
||||
events ! VehicleServiceMessage(
|
||||
s"$cargoActor",
|
||||
VehicleAction.SendResponse(GUID0, PlanetsideAttributeMessage(cargoGUID, 0, cargo.Health))
|
||||
)
|
||||
events ! VehicleServiceMessage(
|
||||
s"$cargoActor",
|
||||
VehicleAction.SendResponse(GUID0, PlanetsideAttributeMessage(cargoGUID, 68, cargo.Shields))
|
||||
)
|
||||
zone.actor ! ZoneActor.AddToBlockMap(cargo, carrier.Position)
|
||||
if (carrier.isFlying) {
|
||||
//the carrier vehicle is flying; eject the cargo vehicle
|
||||
val ejectCargoMsg =
|
||||
CargoMountPointStatusMessage(carrierGUID, GUID0, GUID0, cargoGUID, mountPoint, CargoStatus.InProgress, 0)
|
||||
val detachCargoMsg = ObjectDetachMessage(carrierGUID, cargoGUID, cargoHoldPosition - Vector3.z(1), rotation)
|
||||
val resetCargoMsg =
|
||||
CargoMountPointStatusMessage(carrierGUID, GUID0, GUID0, cargoGUID, mountPoint, CargoStatus.Empty, 0)
|
||||
events ! VehicleServiceMessage(zoneId, VehicleAction.SendResponse(GUID0, ejectCargoMsg))
|
||||
events ! VehicleServiceMessage(zoneId, VehicleAction.SendResponse(GUID0, detachCargoMsg))
|
||||
events ! VehicleServiceMessage(zoneId, VehicleAction.SendResponse(GUID0, resetCargoMsg))
|
||||
log.debug(s"HandleVehicleCargoDismount: eject - $ejectCargoMsg, detach - $detachCargoMsg")
|
||||
if (driverOpt.isEmpty) {
|
||||
//TODO cargo should drop like a rock like normal; until then, deconstruct it
|
||||
cargo.Actor ! Vehicle.Deconstruct()
|
||||
}
|
||||
} else {
|
||||
//the carrier vehicle is not flying; just open the door and let the cargo vehicle back out; force it out if necessary
|
||||
val cargoStatusMessage =
|
||||
CargoMountPointStatusMessage(carrierGUID, GUID0, cargoGUID, GUID0, mountPoint, CargoStatus.InProgress, 0)
|
||||
val cargoDetachMessage =
|
||||
ObjectDetachMessage(carrierGUID, cargoGUID, cargoHoldPosition + Vector3.z(1f), rotation)
|
||||
events ! VehicleServiceMessage(zoneId, VehicleAction.SendResponse(GUID0, cargoStatusMessage))
|
||||
events ! VehicleServiceMessage(zoneId, VehicleAction.SendResponse(GUID0, cargoDetachMessage))
|
||||
driverOpt match {
|
||||
case Some(driver) =>
|
||||
events ! VehicleServiceMessage(
|
||||
s"${driver.Name}",
|
||||
VehicleAction.KickCargo(GUID0, cargo, cargo.Definition.AutoPilotSpeed2, 2500)
|
||||
)
|
||||
//check every quarter second if the vehicle has moved far enough away to be considered dismounted
|
||||
cargoActor ! CheckCargoDismount(carrierGUID, mountPoint, 0)
|
||||
case None =>
|
||||
val resetCargoMsg =
|
||||
CargoMountPointStatusMessage(carrierGUID, GUID0, GUID0, cargoGUID, mountPoint, CargoStatus.Empty, 0)
|
||||
events ! VehicleServiceMessage(zoneId, VehicleAction.SendResponse(GUID0, resetCargoMsg)) //lazy
|
||||
//TODO cargo should back out like normal; until then, deconstruct it
|
||||
cargoActor ! Vehicle.Deconstruct()
|
||||
}
|
||||
}
|
||||
|
||||
case None =>
|
||||
log.warn(s"HandleDismountVehicleCargo: can not locate cargo $cargo in any hold of the carrier vehicle $carrier")
|
||||
}
|
||||
}
|
||||
|
||||
//logging and messaging support functions
|
||||
/**
|
||||
* na
|
||||
* @param decorator custom text for these messages in the log
|
||||
* @param target an optional the target object
|
||||
* @param targetGUID the expected globally unique identifier of the target object
|
||||
*/
|
||||
def LogCargoEventMissingVehicleError(
|
||||
decorator: String,
|
||||
target: Option[PlanetSideGameObject],
|
||||
targetGUID: PlanetSideGUID
|
||||
): Unit = {
|
||||
target match {
|
||||
case Some(_: Vehicle) => ;
|
||||
case Some(_) => log.error(s"$decorator target $targetGUID no longer identifies as a vehicle")
|
||||
case None => log.error(s"$decorator target $targetGUID has gone missing")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
|
||||
* that will set up a realized parent-child association between a ferrying vehicle and a ferried vehicle.
|
||||
* @see `CargoMountPointStatusMessage`
|
||||
* @see `ObjectAttachMessage`
|
||||
* @see `Vehicles.CargoOrientation`
|
||||
* @param carrier the ferrying vehicle
|
||||
* @param cargo the ferried vehicle
|
||||
* @param mountPoint the point on the ferryoing vehicle where the ferried vehicle is attached;
|
||||
* also known as a "cargo hold"
|
||||
* @return a tuple composed of an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
|
||||
*/
|
||||
def CargoMountMessages(
|
||||
carrier: Vehicle,
|
||||
cargo: Vehicle,
|
||||
mountPoint: Int
|
||||
): (ObjectAttachMessage, CargoMountPointStatusMessage) = {
|
||||
CargoMountMessages(carrier.GUID, cargo.GUID, mountPoint, Vehicles.CargoOrientation(cargo))
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
|
||||
* that will set up a realized parent-child association between a ferrying vehicle and a ferried vehicle.
|
||||
* @see `CargoMountPointStatusMessage`
|
||||
* @see `ObjectAttachMessage`
|
||||
* @param carrierGUID the ferrying vehicle
|
||||
* @param cargoGUID the ferried vehicle
|
||||
* @param mountPoint the point on the ferryoing vehicle where the ferried vehicle is attached
|
||||
* @param orientation the positioning of the cargo vehicle in the carrier cargo bay
|
||||
* @return a tuple composed of an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
|
||||
*/
|
||||
def CargoMountMessages(
|
||||
carrierGUID: PlanetSideGUID,
|
||||
cargoGUID: PlanetSideGUID,
|
||||
mountPoint: Int,
|
||||
orientation: Int
|
||||
): (ObjectAttachMessage, CargoMountPointStatusMessage) = {
|
||||
(
|
||||
ObjectAttachMessage(carrierGUID, cargoGUID, mountPoint),
|
||||
CargoMountPointStatusMessage(
|
||||
carrierGUID,
|
||||
cargoGUID,
|
||||
cargoGUID,
|
||||
PlanetSideGUID(0),
|
||||
mountPoint,
|
||||
CargoStatus.Occupied,
|
||||
orientation
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet to all other clients, not this one.
|
||||
* @see `CargoMountPointStatusMessage`
|
||||
* @see `ObjectAttachMessage`
|
||||
* @param carrier the ferrying vehicle
|
||||
* @param cargo the ferried vehicle
|
||||
* @param mountPoint the point on the ferryoing vehicle where the ferried vehicle is attached
|
||||
* @return a tuple composed of an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
|
||||
*/
|
||||
def CargoMountBehaviorForOthers(
|
||||
carrier: Vehicle,
|
||||
cargo: Vehicle,
|
||||
mountPoint: Int,
|
||||
exclude: PlanetSideGUID
|
||||
): (ObjectAttachMessage, CargoMountPointStatusMessage) = {
|
||||
val msgs @ (attachMessage, mountPointStatusMessage) = CargoMountMessages(carrier, cargo, mountPoint)
|
||||
CargoMountMessagesForOthers(carrier.Zone, exclude, attachMessage, mountPointStatusMessage)
|
||||
msgs
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet to all other clients, not this one.
|
||||
* @see `CargoMountPointStatusMessage`
|
||||
* @see `ObjectAttachMessage`
|
||||
* @param attachMessage an `ObjectAttachMessage` packet suitable for initializing cargo operations
|
||||
* @param mountPointStatusMessage a `CargoMountPointStatusMessage` packet suitable for initializing cargo operations
|
||||
*/
|
||||
def CargoMountMessagesForOthers(
|
||||
zone: Zone,
|
||||
exclude: PlanetSideGUID,
|
||||
attachMessage: ObjectAttachMessage,
|
||||
mountPointStatusMessage: CargoMountPointStatusMessage
|
||||
): Unit = {
|
||||
zone.VehicleEvents ! VehicleServiceMessage(zone.id, VehicleAction.SendResponse(exclude, attachMessage))
|
||||
zone.VehicleEvents ! VehicleServiceMessage(zone.id, VehicleAction.SendResponse(exclude, mountPointStatusMessage))
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet to everyone.
|
||||
* @see `CargoMountPointStatusMessage`
|
||||
* @see `ObjectAttachMessage`
|
||||
* @param carrier the ferrying vehicle
|
||||
* @param cargo the ferried vehicle
|
||||
* @param mountPoint the point on the ferryoing vehicle where the ferried vehicle is attached
|
||||
* @return a tuple composed of an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
|
||||
*/
|
||||
def CargoMountBehaviorForAll(
|
||||
carrier: Vehicle,
|
||||
cargo: Vehicle,
|
||||
mountPoint: Int
|
||||
): (ObjectAttachMessage, CargoMountPointStatusMessage) = {
|
||||
val zone = carrier.Zone
|
||||
val zoneId = zone.id
|
||||
val msgs @ (attachMessage, mountPointStatusMessage) = CargoMountMessages(carrier, cargo, mountPoint)
|
||||
zone.VehicleEvents ! VehicleServiceMessage(
|
||||
zoneId,
|
||||
VehicleAction.SendResponse(Service.defaultPlayerGUID, attachMessage)
|
||||
)
|
||||
zone.VehicleEvents ! VehicleServiceMessage(
|
||||
zoneId,
|
||||
VehicleAction.SendResponse(Service.defaultPlayerGUID, mountPointStatusMessage)
|
||||
)
|
||||
msgs
|
||||
}
|
||||
final case class StartCargoMounting(cargo_guid: PlanetSideGUID, cargo_mountpoint: Int)
|
||||
final case class StartCargoDismounting(bailed: Boolean)
|
||||
final case class EndCargoMounting(carrier_guid: PlanetSideGUID)
|
||||
final case class EndCargoDismounting(carrier_guid: PlanetSideGUID)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.serverobject.damage.{Damageable, DamageableVehicle}
|
||||
import net.psforever.objects.vehicles.CargoBehavior
|
||||
import net.psforever.objects.vehicles.{Cargo, CarrierBehavior}
|
||||
import net.psforever.objects.vital.interaction.DamageResult
|
||||
|
||||
/**
|
||||
|
|
@ -13,10 +13,15 @@ import net.psforever.objects.vital.interaction.DamageResult
|
|||
*/
|
||||
class CargoCarrierControl(vehicle: Vehicle)
|
||||
extends VehicleControl(vehicle)
|
||||
with CargoBehavior {
|
||||
def CargoObject = vehicle
|
||||
with CarrierBehavior {
|
||||
def CarrierObject = vehicle
|
||||
|
||||
override def commonEnabledBehavior: Receive = super.commonEnabledBehavior.orElse(cargoBehavior)
|
||||
override def postStop() : Unit = {
|
||||
super.postStop()
|
||||
endAllCarrierOperations()
|
||||
}
|
||||
|
||||
override def commonEnabledBehavior: Receive = super.commonEnabledBehavior.orElse(carrierBehavior)
|
||||
|
||||
/**
|
||||
* If the vehicle becomes disabled, the safety and autonomy of the cargo should be prioritized.
|
||||
|
|
@ -24,21 +29,12 @@ class CargoCarrierControl(vehicle: Vehicle)
|
|||
*/
|
||||
override def PrepareForDisabled(kickPassengers: Boolean) : Unit = {
|
||||
//abandon all cargo
|
||||
vehicle.CargoHolds.values
|
||||
.collect {
|
||||
case hold if hold.isOccupied =>
|
||||
val cargo = hold.occupant.get
|
||||
CargoBehavior.HandleVehicleCargoDismount(
|
||||
cargo.GUID,
|
||||
cargo,
|
||||
vehicle.GUID,
|
||||
vehicle,
|
||||
bailed = false,
|
||||
requestedByPassenger = false,
|
||||
kicked = false
|
||||
)
|
||||
}
|
||||
super.PrepareForDisabled(kickPassengers)
|
||||
vehicle.CargoHolds.collect {
|
||||
case (index, hold : Cargo) if hold.isOccupied =>
|
||||
val cargo = hold.occupant.get
|
||||
checkCargoDismount(cargo.GUID, index, iteration = 0, bailed = false)
|
||||
super.PrepareForDisabled(kickPassengers)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class DeployingVehicleControl(vehicle: Vehicle)
|
|||
case msg : Deployment.TryUndeploy =>
|
||||
deployBehavior.apply(msg)
|
||||
|
||||
case msg @ Mountable.TryDismount(_, seat_num) =>
|
||||
case msg @ Mountable.TryDismount(_, seat_num, _) =>
|
||||
dismountBehavior.apply(msg)
|
||||
dismountCleanup(seat_num)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import net.psforever.objects.serverobject.hackable.GenericHackables
|
|||
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
|
||||
import net.psforever.objects.serverobject.repair.RepairableVehicle
|
||||
import net.psforever.objects.serverobject.terminals.Terminal
|
||||
import net.psforever.objects.vehicles.{AccessPermissionGroup, CargoBehavior, Utility, VehicleLockState}
|
||||
import net.psforever.objects.vehicles._
|
||||
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
||||
import net.psforever.objects.vital.VehicleShieldCharge
|
||||
import net.psforever.objects.vital.environment.EnvironmentReason
|
||||
|
|
@ -51,7 +51,8 @@ class VehicleControl(vehicle: Vehicle)
|
|||
with JammableMountedWeapons
|
||||
with ContainableBehavior
|
||||
with AggravatedBehavior
|
||||
with RespondsToZoneEnvironment {
|
||||
with RespondsToZoneEnvironment
|
||||
with CargoBehavior {
|
||||
//make control actors belonging to utilities when making control actor belonging to vehicle
|
||||
vehicle.Utilities.foreach { case (_, util) => util.Setup }
|
||||
|
||||
|
|
@ -68,6 +69,9 @@ class VehicleControl(vehicle: Vehicle)
|
|||
def ContainerObject = vehicle
|
||||
|
||||
def InteractiveObject = vehicle
|
||||
|
||||
def CargoObject = vehicle
|
||||
|
||||
SetInteraction(EnvironmentAttribute.Water, doInteractingWithWater)
|
||||
SetInteraction(EnvironmentAttribute.Lava, doInteractingWithLava)
|
||||
SetInteraction(EnvironmentAttribute.Death, doInteractingWithDeath)
|
||||
|
|
@ -94,6 +98,7 @@ class VehicleControl(vehicle: Vehicle)
|
|||
util().Actor = Default.Actor
|
||||
}
|
||||
recoverFromEnvironmentInteracting()
|
||||
endAllCargoOperations()
|
||||
}
|
||||
|
||||
def commonEnabledBehavior: Receive = checkBehavior
|
||||
|
|
@ -102,6 +107,7 @@ class VehicleControl(vehicle: Vehicle)
|
|||
.orElse(canBeRepairedByNanoDispenser)
|
||||
.orElse(containerBehavior)
|
||||
.orElse(environmentBehavior)
|
||||
.orElse(cargoBehavior)
|
||||
.orElse {
|
||||
case Vehicle.Ownership(None) =>
|
||||
LoseOwnership()
|
||||
|
|
@ -113,7 +119,7 @@ class VehicleControl(vehicle: Vehicle)
|
|||
mountBehavior.apply(msg)
|
||||
mountCleanup(mount_point, player)
|
||||
|
||||
case msg @ Mountable.TryDismount(_, seat_num) =>
|
||||
case msg @ Mountable.TryDismount(_, seat_num, _) =>
|
||||
dismountBehavior.apply(msg)
|
||||
dismountCleanup(seat_num)
|
||||
|
||||
|
|
@ -247,7 +253,7 @@ class VehicleControl(vehicle: Vehicle)
|
|||
|
||||
def commonDisabledBehavior: Receive = checkBehavior
|
||||
.orElse {
|
||||
case msg @ Mountable.TryDismount(_, seat_num) =>
|
||||
case msg @ Mountable.TryDismount(_, seat_num, _) =>
|
||||
dismountBehavior.apply(msg)
|
||||
dismountCleanup(seat_num)
|
||||
|
||||
|
|
@ -372,13 +378,7 @@ class VehicleControl(vehicle: Vehicle)
|
|||
//escape being someone else's cargo
|
||||
vehicle.MountedIn match {
|
||||
case Some(_) =>
|
||||
CargoBehavior.HandleVehicleCargoDismount(
|
||||
zone,
|
||||
guid,
|
||||
bailed = false,
|
||||
requestedByPassenger = false,
|
||||
kicked = false
|
||||
)
|
||||
startCargoDismounting(bailed = false)
|
||||
case _ => ;
|
||||
}
|
||||
if (!vehicle.isFlying || kickPassengers) {
|
||||
|
|
@ -386,13 +386,13 @@ class VehicleControl(vehicle: Vehicle)
|
|||
vehicle.Seats.values.foreach { seat =>
|
||||
seat.occupant match {
|
||||
case Some(player) =>
|
||||
seat.unmount(player)
|
||||
seat.unmount(player, BailType.Kicked)
|
||||
player.VehicleSeated = None
|
||||
if (player.isAlive) {
|
||||
zone.actor ! ZoneActor.AddToBlockMap(player, vehicle.Position)
|
||||
}
|
||||
if (player.HasGUID) {
|
||||
events ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(player.GUID, 4, false, guid))
|
||||
events ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(player.GUID, 4, true, guid))
|
||||
}
|
||||
case None => ;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
/**
|
||||
* damage that is inherent to the object, used for explosions and collisions, mainly
|
||||
* damage that is inherent to the object, used for explosions, mainly
|
||||
*/
|
||||
var innateDamage: Option[DamageWithPosition] = None
|
||||
|
||||
|
|
@ -168,4 +168,8 @@ trait VitalityDefinition extends DamageModifiers {
|
|||
innateDamage = Some(combustion)
|
||||
innateDamage
|
||||
}
|
||||
|
||||
val collision: CollisionData = new CollisionData()
|
||||
|
||||
var mass: Float = 1f
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ object DamageResolution extends Enumeration {
|
|||
AggravatedSplashBurn, //continuous splashed aggravated damage
|
||||
Explosion, //area of effect damage caused by an internal mechanism; unrelated to Splash
|
||||
Environmental, //died to environmental causes
|
||||
Suicide //i don't want to be the one the battles always choose
|
||||
Suicide, //i don't want to be the one the battles always choose
|
||||
Collision //went splat
|
||||
= Value
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
package net.psforever.objects.vital.collision
|
||||
|
||||
import net.psforever.objects.ballistics.SourceEntry
|
||||
import net.psforever.objects.vital.base.{DamageReason, DamageResolution}
|
||||
import net.psforever.objects.ballistics.{DeployableSource, SourceEntry, VehicleSource}
|
||||
import net.psforever.objects.vital.base.{DamageModifiers, DamageReason, DamageResolution, DamageType}
|
||||
import net.psforever.objects.vital.prop.DamageProperties
|
||||
import net.psforever.objects.vital.resolution.DamageAndResistance
|
||||
import net.psforever.types.Vector3
|
||||
|
||||
/**
|
||||
* A wrapper for a "damage source" in damage calculations
|
||||
* that parameterizes information necessary to explain a collision.
|
||||
* Being "adversarial" requires that the damage be performed as an aggressive action between individuals.
|
||||
* @param source na
|
||||
* Common base for reporting damage for reasons of collisions.
|
||||
*/
|
||||
final case class AdversarialCollisionReason(source: DamageProperties) extends DamageReason {
|
||||
def resolution: DamageResolution.Value = DamageResolution.Unresolved
|
||||
trait CausedByColliding
|
||||
extends DamageReason {
|
||||
def resolution: DamageResolution.Value = DamageResolution.Collision
|
||||
|
||||
def same(test: DamageReason): Boolean = false
|
||||
def source: DamageProperties = CollisionReason.noDamage
|
||||
|
||||
def damageModel: DamageAndResistance = null
|
||||
def velocity: Vector3
|
||||
|
||||
override def adversary : Option[SourceEntry] = None
|
||||
def fall: Float
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper for a "damage source" in damage calculations
|
||||
* that parameterizes information necessary to explain a collision.
|
||||
* @param source na
|
||||
* A wrapper for a "damage source" in damage calculations that explains a collision.
|
||||
* @param velocity how fast the target is moving prior to the collision
|
||||
* @param fall ongoing vertical displacement since before the collision
|
||||
* @param damageModel the functionality that is necessary for interaction
|
||||
* of a vital game object with the rest of the hostile game world
|
||||
*/
|
||||
final case class CollisionReason(source: DamageProperties) extends DamageReason {
|
||||
def resolution: DamageResolution.Value = DamageResolution.Unresolved
|
||||
final case class CollisionReason(
|
||||
velocity: Vector3,
|
||||
fall: Float,
|
||||
damageModel: DamageAndResistance
|
||||
) extends CausedByColliding {
|
||||
def same(test: DamageReason): Boolean = test match {
|
||||
case cr: CollisionReason => cr.velocity == velocity && math.abs(cr.fall - fall) < 0.05f
|
||||
case _ => false
|
||||
}
|
||||
|
||||
def same(test: DamageReason): Boolean = false
|
||||
override def adversary: Option[SourceEntry] = None
|
||||
|
||||
def damageModel: DamageAndResistance = null
|
||||
|
||||
override def adversary : Option[SourceEntry] = None
|
||||
override def unstructuredModifiers: List[DamageModifiers.Mod] = List(
|
||||
GroundImpact,
|
||||
HeadonImpact
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper for a "damage source" in damage calculations that augment collision information
|
||||
* by providing information about a qualified target that was struck.
|
||||
* @param cause information about the collision
|
||||
* @param collidedWith information regarding the qualified target that was struck
|
||||
*/
|
||||
final case class CollisionWithReason(
|
||||
cause: CollisionReason,
|
||||
collidedWith: SourceEntry
|
||||
) extends CausedByColliding {
|
||||
def same(test: DamageReason): Boolean = test match {
|
||||
case cr: CollisionWithReason =>
|
||||
cr.cause.same(cause) && cr.collidedWith == collidedWith
|
||||
case _ =>
|
||||
false
|
||||
}
|
||||
|
||||
def velocity: Vector3 = cause.velocity
|
||||
|
||||
def fall: Float = cause.fall
|
||||
|
||||
def damageModel: DamageAndResistance = cause.damageModel
|
||||
|
||||
override def adversary: Option[SourceEntry] = {
|
||||
collidedWith match {
|
||||
case v: VehicleSource =>
|
||||
v.occupants.head match {
|
||||
case SourceEntry.None => Some(collidedWith)
|
||||
case e => Some(e)
|
||||
}
|
||||
case d: DeployableSource =>
|
||||
d.owner match {
|
||||
case SourceEntry.None => Some(collidedWith)
|
||||
case e => Some(e)
|
||||
}
|
||||
case _ =>
|
||||
Some(collidedWith)
|
||||
}
|
||||
}
|
||||
|
||||
override def unstructuredModifiers: List[DamageModifiers.Mod] = List(
|
||||
GroundImpactWith,
|
||||
HeadonImpactWithEntity
|
||||
) ++ collidedWith.Definition.Modifiers
|
||||
|
||||
override def attribution : Int = collidedWith.Definition.ObjectId
|
||||
}
|
||||
|
||||
object CollisionReason {
|
||||
/** The flags for calculating an absence of conventional damage for collision.
|
||||
* Damage is considered `Direct`, however, which defines some resistance. */
|
||||
val noDamage = new DamageProperties {
|
||||
CausesDamageType = DamageType.Direct
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import net.psforever.objects.serverobject.environment.PieceOfEnvironment
|
|||
import net.psforever.objects.serverobject.structures.FoundationBuilder
|
||||
import net.psforever.objects.serverobject.zipline.ZipLinePath
|
||||
import net.psforever.objects.serverobject.{PlanetSideServerObject, ServerObjectBuilder}
|
||||
import net.psforever.types.Vector3
|
||||
|
||||
/**
|
||||
* The fixed instantiation and relation of a series of server objects.<br>
|
||||
|
|
@ -129,4 +130,8 @@ class ZoneMap(val name: String) {
|
|||
lattice = lattice ++ Set((source, target))
|
||||
}
|
||||
|
||||
def areValidCoordinates(coordinates: Vector3): Boolean = {
|
||||
coordinates.x >= 0 && coordinates.x <= scale.width &&
|
||||
coordinates.y >= 0 && coordinates.y <= scale.height
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -230,7 +230,7 @@ class BlockMap(fullMapWidth: Int, fullMapHeight: Int, desiredSpanSize: Int) {
|
|||
def move(target: BlockMapEntity, toPosition: Vector3): SectorPopulation = {
|
||||
target.blockMapEntry match {
|
||||
case Some(entry) => move(target, toPosition, entry.coords, entry.range)
|
||||
case None => SectorGroup(Nil)
|
||||
case _ => SectorGroup(Nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -82,11 +82,15 @@ object BlockMapEntity {
|
|||
* To properly update the range, perform a proper update.)
|
||||
* @param target the entity on the blockmap
|
||||
* @param newCoords the world coordinates of the entity, the position to which it is moving / being moved
|
||||
* @return always `true`; we are updating this entry
|
||||
* @return `true`, if we are updating this entry; `false`, otherwsie
|
||||
*/
|
||||
private def updateBlockMap(target: BlockMapEntity, newCoords: Vector3): Boolean = {
|
||||
val oldEntry = target.blockMapEntry.get
|
||||
target.blockMapEntry = Some(BlockMapEntry(newCoords, oldEntry.range, oldEntry.sectors))
|
||||
true
|
||||
target.blockMapEntry match {
|
||||
case Some(oldEntry) =>
|
||||
target.blockMapEntry = Some(BlockMapEntry(newCoords, oldEntry.range, oldEntry.sectors))
|
||||
true
|
||||
case None =>
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,11 +33,11 @@ object DamageWithPositionMessage extends Marshallable[DamageWithPositionMessage]
|
|||
).xmap[DamageWithPositionMessage] (
|
||||
{
|
||||
case unk :: pos :: HNil =>
|
||||
DamageWithPositionMessage(math.max(0, math.min(unk, 255)), pos)
|
||||
DamageWithPositionMessage(unk, pos)
|
||||
},
|
||||
{
|
||||
case DamageWithPositionMessage(unk, pos) =>
|
||||
unk :: pos :: HNil
|
||||
math.max(0, math.min(unk, 255)) :: pos :: HNil
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,15 @@ import scodec.Codec
|
|||
import scodec.codecs._
|
||||
|
||||
/**
|
||||
* Note: For some reason this packet does not include the GUID of the vehicle that is being dismounted from. As a workaround vehicle.MountedIn in manually set/removed
|
||||
* @param player_guid // GUID of the player that is rqeuesting dismount
|
||||
* @param vehicle_guid GUID of the vehicle that is requesting dismount
|
||||
* @param bailed If the vehicle bailed out of the cargo vehicle
|
||||
* @param requestedByPassenger If a passenger of the vehicle in the cargo bay requests dismount this bit will be set
|
||||
* @param kicked If the vehicle was kicked by the cargo vehicle pilot
|
||||
* Request dismount of one vehicle (cargo) that is being ferried by another vehicle (carrier).
|
||||
* The carrier has what is called a "cargo bay" which is where the cargo is being stored for ferrying.
|
||||
* @param player_guid GUID of the player that is rqeuesting dismount;
|
||||
* when kicked by carrier driver, player_guid will be PlanetSideGUID(0);
|
||||
* when exiting of the cargo vehicle driver's own accord, player_guid will be the cargo vehicle driver
|
||||
* @param vehicle_guid GUID of the vehicle that is requesting dismount (cargo)
|
||||
* @param bailed if the cargo vehicle bailed out of the cargo vehicle
|
||||
* @param requestedByPassenger if a passenger of the cargo vehicle requests dismount
|
||||
* @param kicked if the cargo vehicle was kicked by the cargo vehicle pilot
|
||||
*/
|
||||
final case class DismountVehicleCargoMsg(
|
||||
player_guid: PlanetSideGUID,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,27 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.packet.game
|
||||
|
||||
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
|
||||
import enumeratum.values.{IntEnum, IntEnumEntry}
|
||||
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
|
||||
import net.psforever.types.{PlanetSideGUID, Vector3}
|
||||
import scodec.Attempt.Successful
|
||||
import scodec.Codec
|
||||
import scodec.codecs._
|
||||
import shapeless.{::, HNil}
|
||||
|
||||
sealed abstract class CollisionIs(val value: Int) extends IntEnumEntry
|
||||
|
||||
object CollisionIs extends IntEnum[CollisionIs] {
|
||||
val values = findValues
|
||||
|
||||
case object OfGroundVehicle extends CollisionIs(value = 0)
|
||||
|
||||
case object OfAircraft extends CollisionIs(value = 1)
|
||||
|
||||
case object OfInfantry extends CollisionIs(value = 2)
|
||||
|
||||
case object BetweenThings extends CollisionIs(value = 3) //currently, invalid
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatched by the client when the player has encountered a physical interaction that would cause damage.<br>
|
||||
|
|
@ -13,7 +30,7 @@ import scodec.codecs._
|
|||
* The first is the `player`, that is, the client's avatar.
|
||||
* The second is the `target` with respect to the `player` - whatever the avatar ran into, or whatever ran into the avatar.
|
||||
* In the case of isolated forms of collision such as fall damage the `target` fields are blank or zero'd.
|
||||
* @param unk1 na
|
||||
* @param collision_type a brief hint at the sort of interaction
|
||||
* @param player the player or player-controlled vehicle
|
||||
* @param target the other party in the collision
|
||||
* @param player_health the player's health
|
||||
|
|
@ -22,42 +39,56 @@ import scodec.codecs._
|
|||
* @param target_velocity the target's velocity
|
||||
* @param player_pos the player's world coordinates
|
||||
* @param target_pos the target's world coordinates
|
||||
* @param unk1 na
|
||||
* @param unk2 na
|
||||
* @param unk3 na
|
||||
* @param unk4 na
|
||||
*/
|
||||
final case class GenericCollisionMsg(
|
||||
unk1: Int,
|
||||
player: PlanetSideGUID,
|
||||
target: PlanetSideGUID,
|
||||
player_health: Int,
|
||||
target_health: Int,
|
||||
player_velocity: Vector3,
|
||||
target_velocity: Vector3,
|
||||
player_pos: Vector3,
|
||||
target_pos: Vector3,
|
||||
unk2: Long,
|
||||
unk3: Long,
|
||||
unk4: Long
|
||||
) extends PlanetSideGamePacket {
|
||||
collision_type: CollisionIs,
|
||||
player: PlanetSideGUID,
|
||||
player_health: Int,
|
||||
player_pos: Vector3,
|
||||
player_velocity: Vector3,
|
||||
target: PlanetSideGUID,
|
||||
target_health: Int,
|
||||
target_pos: Vector3,
|
||||
target_velocity: Vector3,
|
||||
unk1: Long,
|
||||
unk2: Long,
|
||||
unk3: Long
|
||||
) extends PlanetSideGamePacket {
|
||||
type Packet = GenericCollisionMsg
|
||||
def opcode = GamePacketOpcode.GenericCollisionMsg
|
||||
def encode = GenericCollisionMsg.encode(this)
|
||||
}
|
||||
|
||||
object GenericCollisionMsg extends Marshallable[GenericCollisionMsg] {
|
||||
private val collisionIsCodec: Codec[CollisionIs] = PacketHelpers.createIntEnumCodec(CollisionIs, uint2)
|
||||
|
||||
private val velocityFloatCodec: Codec[Vector3] = Vector3.codec_float
|
||||
.narrow[Vector3](a => Successful(a * 3.6f), a => a * 0.2777778f) //this precision is necessary
|
||||
|
||||
implicit val codec: Codec[GenericCollisionMsg] = (
|
||||
("unk1" | uint2) ::
|
||||
("p" | PlanetSideGUID.codec) ::
|
||||
("t" | PlanetSideGUID.codec) ::
|
||||
("p_health" | uint16L) ::
|
||||
("t_health" | uint16L) ::
|
||||
("p_vel" | Vector3.codec_float) ::
|
||||
("t_vel" | Vector3.codec_float) ::
|
||||
("p_pos" | Vector3.codec_pos) ::
|
||||
("t_pos" | Vector3.codec_pos) ::
|
||||
("unk2" | uint32L) ::
|
||||
("unk3" | uint32L) ::
|
||||
("unk4" | uint32L)
|
||||
).as[GenericCollisionMsg]
|
||||
("collision_type" | collisionIsCodec) ::
|
||||
("p" | PlanetSideGUID.codec) ::
|
||||
("t" | PlanetSideGUID.codec) ::
|
||||
("p_health" | uint16L) ::
|
||||
("t_health" | uint16L) ::
|
||||
("p_vel" | velocityFloatCodec) ::
|
||||
("t_vel" | velocityFloatCodec) ::
|
||||
("p_pos" | Vector3.codec_pos) ::
|
||||
("t_pos" | Vector3.codec_pos) ::
|
||||
("unk1" | uint32L) ::
|
||||
("unk2" | uint32L) ::
|
||||
("unk3" | uint32L)
|
||||
).xmap[GenericCollisionMsg](
|
||||
{
|
||||
case ct :: p :: t :: ph :: th :: pv :: tv :: pp :: tp :: u1 :: u2 :: u3 :: HNil =>
|
||||
GenericCollisionMsg(ct, p, ph, pp, pv, t, th, tp, tv, u1, u2, u3)
|
||||
},
|
||||
{
|
||||
case GenericCollisionMsg(ct, p, ph, pp, pv, t, th, tp, tv, u1, u2, u3) =>
|
||||
ct :: p :: t :: ph :: th :: pv :: tv :: pp :: tp :: u1 :: u2 :: u3 :: HNil
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import scodec.codecs._
|
|||
* @param player_guid The guid of the player sending the request to board another vehicle with a cargo vehicle
|
||||
* @param vehicle_guid The guid of the vehicle for the requesting player
|
||||
* @param target_vehicle The cargo vehicle guid e.g. Galaxy / Lodestar
|
||||
* @param unk4
|
||||
* @param unk4 na
|
||||
*/
|
||||
final case class MountVehicleCargoMsg(
|
||||
player_guid: PlanetSideGUID,
|
||||
|
|
|
|||
|
|
@ -20,7 +20,9 @@ import scodec.codecs._
|
|||
* in repeating order: 13, 14, 10, 11, 12, 15;
|
||||
* `None`, when landed and for all vehicles that do not fly
|
||||
* @param unk3 na
|
||||
* @param unk4 na
|
||||
* @param unk4 na;
|
||||
* 0 by default;
|
||||
* for mosquito, can become various numbers during collision damage
|
||||
* @param wheel_direction for ground vehicles, whether and how much the wheels are being turned;
|
||||
* 15 for straight;
|
||||
* 0 for hard right;
|
||||
|
|
@ -80,15 +82,15 @@ object VehicleStateMessage extends Marshallable[VehicleStateMessage] {
|
|||
|
||||
implicit val codec: Codec[VehicleStateMessage] = (
|
||||
("vehicle_guid" | PlanetSideGUID.codec) ::
|
||||
("unk1" | uintL(3)) ::
|
||||
("pos" | Vector3.codec_pos) ::
|
||||
("ang" | codec_orient) ::
|
||||
optional(bool, "vel" | Vector3.codec_vel) ::
|
||||
optional(bool, "flying" | uintL(5)) ::
|
||||
("unk3" | uintL(7)) ::
|
||||
("unk4" | uint4L) ::
|
||||
("wheel_direction" | uintL(5)) ::
|
||||
("int5" | bool) ::
|
||||
("is_cloaked" | bool)
|
||||
("unk1" | uintL(bits = 3)) ::
|
||||
("pos" | Vector3.codec_pos) ::
|
||||
("ang" | codec_orient) ::
|
||||
("vel" | optional(bool, Vector3.codec_vel)) ::
|
||||
("flying" | optional(bool, uintL(bits = 5))) ::
|
||||
("unk3" | uintL(bits = 7)) ::
|
||||
("unk4" | uint4L) ::
|
||||
("wheel_direction" | uintL(5)) ::
|
||||
("is_decelerating" | bool) ::
|
||||
("is_cloaked" | bool)
|
||||
).as[VehicleStateMessage]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
package net.psforever.types
|
||||
|
||||
import net.psforever.newcodecs._
|
||||
import scodec.Attempt.Successful
|
||||
import scodec.Codec
|
||||
import scodec.codecs._
|
||||
|
||||
|
|
@ -38,6 +39,18 @@ final case class Vector3(x: Float, y: Float, z: Float) {
|
|||
Vector3(x * scalar, y * scalar, z * scalar)
|
||||
}
|
||||
|
||||
/**
|
||||
* Operator for vector scaling, treating `Vector3` objects as actual mathematical vectors.
|
||||
* The application of this overload is "vector / scalar" exclusively.
|
||||
* "scalar / vector" is invalid.
|
||||
* Due to rounding, may not be perfectly equivalent to "vector * ( 1 / scalar )".
|
||||
* @param scalar the value to divide this vector
|
||||
* @return a new `Vector3` object
|
||||
*/
|
||||
def /(scalar: Float): Vector3 = {
|
||||
Vector3(x / scalar, y / scalar, z / scalar)
|
||||
}
|
||||
|
||||
/**
|
||||
* Operator for returning the ground-planar coordinates
|
||||
* and ignoring the perpendicular distance from the world floor.
|
||||
|
|
@ -100,7 +113,7 @@ object Vector3 {
|
|||
("x" | newcodecs.q_float(-256.0, 256.0, 14)) ::
|
||||
("y" | newcodecs.q_float(-256.0, 256.0, 14)) ::
|
||||
("z" | newcodecs.q_float(-256.0, 256.0, 14))
|
||||
).as[Vector3]
|
||||
).as[Vector3].narrow(a => Successful(a * 3.6f), a => a * 0.2778f)
|
||||
|
||||
implicit val codec_float: Codec[Vector3] = (
|
||||
("x" | floatL) ::
|
||||
|
|
|
|||
|
|
@ -50,11 +50,11 @@ class CodecTest extends Specification {
|
|||
val string_vel = hex"857D4E0FFFC0"
|
||||
|
||||
"decode" in {
|
||||
Vector3.codec_vel.decode(string_vel.bits).require.value mustEqual Vector3(-3.84375f, 2.59375f, 255.96875f)
|
||||
Vector3.codec_vel.decode(string_vel.bits).require.value mustEqual Vector3(-13.8375f, 9.3375f, 921.4875f)
|
||||
}
|
||||
|
||||
"encode" in {
|
||||
Vector3.codec_vel.encode(Vector3(-3.84375f, 2.59375f, 255.96875f)).require.bytes mustEqual string_vel
|
||||
Vector3.codec_vel.encode(Vector3(-13.8375f, 9.3375f, 921.4875f)).require.bytes mustEqual string_vel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class FireHintMessageTest extends Specification {
|
|||
u2 mustEqual 65399
|
||||
u3 mustEqual 7581
|
||||
u4 mustEqual 0
|
||||
u5 mustEqual None
|
||||
u5.isEmpty mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -34,7 +34,7 @@ class FireHintMessageTest extends Specification {
|
|||
u2 mustEqual 65231
|
||||
u3 mustEqual 64736
|
||||
u4 mustEqual 3
|
||||
u5 mustEqual Some(Vector3(21.5f, -6.8125f, 2.65625f))
|
||||
u5.contains(Vector3(77.4f, -24.525f, 9.5625f)) mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -46,7 +46,7 @@ class FireHintMessageTest extends Specification {
|
|||
|
||||
pkt mustEqual string
|
||||
}
|
||||
"encode string2" in {
|
||||
"encode (string2)" in {
|
||||
val msg = FireHintMessage(
|
||||
PlanetSideGUID(3592),
|
||||
Vector3(2910.789f, 3744.875f, 69.0625f),
|
||||
|
|
@ -54,7 +54,7 @@ class FireHintMessageTest extends Specification {
|
|||
65231,
|
||||
64736,
|
||||
3,
|
||||
Some(Vector3(21.5f, -6.8125f, 2.65625f))
|
||||
Some(Vector3(77.4f, -24.525f, 9.5625f))
|
||||
)
|
||||
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||
|
||||
|
|
|
|||
|
|
@ -11,17 +11,18 @@ class GenericCollisionMsgTest extends Specification {
|
|||
//TODO find a better test later
|
||||
val string =
|
||||
hex"3C 92C00000190000001B2A8010932CEF505C70946F00000000000000000000000017725EBC6D6A058000000000000000000000000000003F8FF45140"
|
||||
|
||||
"decode" in {
|
||||
PacketCoding.decodePacket(string).require match {
|
||||
case GenericCollisionMsg(unk1, p, t, php, thp, pv, tv, ppos, tpos, unk2, unk3, unk4) =>
|
||||
unk1 mustEqual 2
|
||||
case GenericCollisionMsg(ct, p, php, ppos, pv, t, thp, tpos, tv, unk2, unk3, unk4) =>
|
||||
ct mustEqual CollisionIs.OfInfantry
|
||||
p mustEqual PlanetSideGUID(75)
|
||||
t mustEqual PlanetSideGUID(0)
|
||||
php mustEqual 100
|
||||
thp mustEqual 0
|
||||
pv.x mustEqual 32.166428f
|
||||
pv.y mustEqual 23.712547f
|
||||
pv.z mustEqual -0.012802706f
|
||||
pv.x mustEqual 115.79913f
|
||||
pv.y mustEqual 85.365166f
|
||||
pv.z mustEqual -0.046089742f
|
||||
tv.x mustEqual 0.0f
|
||||
tv.z mustEqual 0.0f
|
||||
tv.x mustEqual 0.0f
|
||||
|
|
@ -38,22 +39,48 @@ class GenericCollisionMsgTest extends Specification {
|
|||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"encode" in {
|
||||
val msg = GenericCollisionMsg(
|
||||
2,
|
||||
CollisionIs.OfInfantry,
|
||||
PlanetSideGUID(75),
|
||||
PlanetSideGUID(0),
|
||||
100,
|
||||
0,
|
||||
Vector3(32.166428f, 23.712547f, -0.012802706f),
|
||||
Vector3(0.0f, 0.0f, 0.0f),
|
||||
Vector3(3986.7266f, 2615.3672f, 90.625f),
|
||||
Vector3(115.79913f, 85.365166f, -0.046089742f),
|
||||
PlanetSideGUID(0),
|
||||
0,
|
||||
Vector3(0.0f, 0.0f, 0.0f),
|
||||
Vector3(0.0f, 0.0f, 0.0f),
|
||||
0L,
|
||||
0L,
|
||||
1171341310L
|
||||
)
|
||||
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||
pkt mustEqual string
|
||||
//pkt mustEqual string
|
||||
PacketCoding.decodePacket(pkt).require match {
|
||||
case GenericCollisionMsg(ct, p, php, ppos, pv, t, thp, tpos, tv, unk2, unk3, unk4) =>
|
||||
ct mustEqual CollisionIs.OfInfantry
|
||||
p mustEqual PlanetSideGUID(75)
|
||||
t mustEqual PlanetSideGUID(0)
|
||||
php mustEqual 100
|
||||
thp mustEqual 0
|
||||
pv.x mustEqual 115.79913f
|
||||
pv.y mustEqual 85.365166f
|
||||
pv.z mustEqual -0.046089742f
|
||||
tv.x mustEqual 0.0f
|
||||
tv.z mustEqual 0.0f
|
||||
tv.x mustEqual 0.0f
|
||||
ppos.x mustEqual 3986.7266f
|
||||
ppos.y mustEqual 2615.3672f
|
||||
ppos.z mustEqual 90.625f
|
||||
tpos.x mustEqual 0.0f
|
||||
tpos.y mustEqual 0.0f
|
||||
tpos.z mustEqual 0.0f
|
||||
unk2 mustEqual 0L
|
||||
unk3 mustEqual 0L
|
||||
unk4 mustEqual 1171341310L
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ class LongRangeProjectileInfoMessageTest extends Specification {
|
|||
case LongRangeProjectileInfoMessage(guid, pos, vel) =>
|
||||
guid mustEqual PlanetSideGUID(5330)
|
||||
pos mustEqual Vector3(2264, 5115.039f, 31.046875f)
|
||||
vel.contains(Vector3(-57.1875f, 9.875f, 47.5f)) mustEqual true
|
||||
vel.contains(Vector3(-205.875f, 35.55f, 171.0f)) mustEqual true
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@ class LongRangeProjectileInfoMessageTest extends Specification {
|
|||
val msg = LongRangeProjectileInfoMessage(
|
||||
PlanetSideGUID(5330),
|
||||
Vector3(2264, 5115.039f, 31.046875f),
|
||||
Vector3(-57.1875f, 9.875f, 47.5f)
|
||||
Vector3(-205.875f, 35.55f, 171.0f)
|
||||
)
|
||||
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||
pkt mustEqual string
|
||||
|
|
|
|||
|
|
@ -99,8 +99,8 @@ class PlayerStateMessageTest extends Specification {
|
|||
pos.y mustEqual 5987.6016f
|
||||
pos.z mustEqual 44.1875f
|
||||
vel.isDefined mustEqual true
|
||||
vel.get.x mustEqual 2.53125f
|
||||
vel.get.y mustEqual 6.5625f
|
||||
vel.get.x mustEqual 9.1125f
|
||||
vel.get.y mustEqual 23.625f
|
||||
vel.get.z mustEqual 0.0f
|
||||
facingYaw mustEqual 22.5f
|
||||
facingPitch mustEqual -11.25f
|
||||
|
|
@ -155,7 +155,7 @@ class PlayerStateMessageTest extends Specification {
|
|||
val msg = PlayerStateMessage(
|
||||
PlanetSideGUID(1696),
|
||||
Vector3(4008.6016f, 5987.6016f, 44.1875f),
|
||||
Some(Vector3(2.53125f, 6.5625f, 0f)),
|
||||
Some(Vector3(9.1125f, 23.625f, 0f)),
|
||||
22.5f,
|
||||
-11.25f,
|
||||
0f,
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class PlayerStateMessageUpstreamTest extends Specification {
|
|||
) =>
|
||||
avatar_guid mustEqual PlanetSideGUID(75)
|
||||
pos mustEqual Vector3(3694.1094f, 2735.4531f, 90.84375f)
|
||||
vel.contains(Vector3(4.375f, 2.59375f, 0.0f)) mustEqual true
|
||||
vel.contains(Vector3(15.75f, 9.3375f, 0.0f)) mustEqual true
|
||||
facingYaw mustEqual 61.875f
|
||||
facingPitch mustEqual -8.4375f
|
||||
facingYawUpper mustEqual 0.0f
|
||||
|
|
@ -51,7 +51,7 @@ class PlayerStateMessageUpstreamTest extends Specification {
|
|||
val msg = PlayerStateMessageUpstream(
|
||||
PlanetSideGUID(75),
|
||||
Vector3(3694.1094f, 2735.4531f, 90.84375f),
|
||||
Some(Vector3(4.375f, 2.59375f, 0.0f)),
|
||||
Some(Vector3(15.75f, 9.3375f, 0.0f)),
|
||||
61.875f,
|
||||
-8.4375f,
|
||||
0.0f,
|
||||
|
|
|
|||
|
|
@ -49,9 +49,9 @@ class PlayerStateShiftMessageTest extends Specification {
|
|||
state.get.pos.z mustEqual 50.3125f
|
||||
state.get.viewYawLim mustEqual 50.625f
|
||||
state.get.vel.isDefined mustEqual true
|
||||
state.get.vel.get.x mustEqual 2.8125f
|
||||
state.get.vel.get.y mustEqual -8.0f
|
||||
state.get.vel.get.z mustEqual 0.375f
|
||||
state.get.vel.get.x mustEqual 10.125f
|
||||
state.get.vel.get.y mustEqual -28.8f
|
||||
state.get.vel.get.z mustEqual 1.3499999f
|
||||
unk.isDefined mustEqual false
|
||||
case _ =>
|
||||
ko
|
||||
|
|
@ -74,7 +74,7 @@ class PlayerStateShiftMessageTest extends Specification {
|
|||
|
||||
"encode (pos and vel)" in {
|
||||
val msg = PlayerStateShiftMessage(
|
||||
ShiftState(2, Vector3(4645.75f, 5811.6016f, 50.3125f), 50.625f, Vector3(2.8125f, -8.0f, 0.375f))
|
||||
ShiftState(2, Vector3(4645.75f, 5811.6016f, 50.3125f), 50.625f, Vector3(10.125f, -28.8f, 1.3499999f))
|
||||
)
|
||||
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||
|
||||
|
|
|
|||
|
|
@ -21,10 +21,7 @@ class SplashHitMessageTest extends Specification {
|
|||
projectile_pos.z mustEqual 90.921875f
|
||||
unk2 mustEqual 0
|
||||
unk3 mustEqual 0
|
||||
projectile_vel.isDefined mustEqual true
|
||||
projectile_vel.get.x mustEqual 2.21875f
|
||||
projectile_vel.get.y mustEqual 0.90625f
|
||||
projectile_vel.get.z mustEqual -1.125f
|
||||
projectile_vel.contains(Vector3(7.9874997f, 3.2624998f, -4.0499997f)) mustEqual true
|
||||
unk4.isDefined mustEqual false
|
||||
targets.size mustEqual 2
|
||||
//0
|
||||
|
|
@ -53,7 +50,7 @@ class SplashHitMessageTest extends Specification {
|
|||
Vector3(3681.3438f, 2728.9062f, 90.921875f),
|
||||
0,
|
||||
0,
|
||||
Some(Vector3(2.21875f, 0.90625f, -1.125f)),
|
||||
Some(Vector3(7.9874997f, 3.2624998f, -4.0499997f)),
|
||||
None,
|
||||
SplashedTarget(PlanetSideGUID(75), Vector3(3674.8438f, 2726.789f, 91.15625f), 286326784L, None) ::
|
||||
SplashedTarget(PlanetSideGUID(372), Vector3(3679.1328f, 2722.6016f, 92.765625f), 268435456L, None) ::
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class VehicleStateMessageTest extends Specification {
|
|||
vel.isDefined mustEqual true
|
||||
vel.get.x mustEqual 0.0f
|
||||
vel.get.y mustEqual 0.0f
|
||||
vel.get.z mustEqual 0.03125f
|
||||
vel.get.z mustEqual 0.1125f
|
||||
unk2.isDefined mustEqual false
|
||||
unk3 mustEqual 0
|
||||
unk4 mustEqual 0
|
||||
|
|
@ -42,7 +42,7 @@ class VehicleStateMessageTest extends Specification {
|
|||
0,
|
||||
Vector3(3674.8438f, 2726.789f, 91.09375f),
|
||||
Vector3(359.29688f, 1.0546875f, 90.35156f),
|
||||
Some(Vector3(0.0f, 0.0f, 0.03125f)),
|
||||
Some(Vector3(0.0f, 0.0f, 0.1125f)),
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class VehicleSubStateMessageTest extends Specification {
|
|||
vehicle_pos mustEqual Vector3(3465.9575f, 2873.3635f, 91.05253f)
|
||||
vehicle_ang mustEqual Vector3(11.6015625f, 0.0f, 3.515625f)
|
||||
vel.isDefined mustEqual true
|
||||
vel.get mustEqual Vector3(-0.40625f, 0.03125f, -0.8125f)
|
||||
vel.contains(Vector3(-1.4625f, 0.1125f, -2.925f)) mustEqual true
|
||||
unk1 mustEqual false
|
||||
unk2.isDefined mustEqual false
|
||||
case _ =>
|
||||
|
|
@ -32,7 +32,7 @@ class VehicleSubStateMessageTest extends Specification {
|
|||
PlanetSideGUID(3376),
|
||||
Vector3(3465.9575f, 2873.3635f, 91.05253f),
|
||||
Vector3(11.6015625f, 0.0f, 3.515625f),
|
||||
Some(Vector3(-0.40625f, 0.03125f, -0.8125f)),
|
||||
Some(Vector3(-1.4625f, 0.1125f, -2.925f)),
|
||||
false,
|
||||
None
|
||||
)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class CharacterDataTest extends Specification {
|
|||
pos.coord mustEqual Vector3(3674.8438f, 2726.789f, 91.15625f)
|
||||
pos.orient mustEqual Vector3(0f, 0f, 64.6875f)
|
||||
pos.vel.isDefined mustEqual true
|
||||
pos.vel.get mustEqual Vector3(1.4375f, -0.4375f, 0f)
|
||||
pos.vel.get mustEqual Vector3(5.1749997f, -1.5749999f, 0.0f)
|
||||
|
||||
basic match {
|
||||
case CharacterAppearanceData(a, b, ribbons) =>
|
||||
|
|
@ -294,7 +294,7 @@ class CharacterDataTest extends Specification {
|
|||
val pos: PlacementData = PlacementData(
|
||||
Vector3(3674.8438f, 2726.789f, 91.15625f),
|
||||
Vector3(0f, 0f, 64.6875f),
|
||||
Some(Vector3(1.4375f, -0.4375f, 0f))
|
||||
Some(Vector3(5.1749997f, -1.5749999f, 0.0f))
|
||||
)
|
||||
val a: Int => CharacterAppearanceA = CharacterAppearanceA(
|
||||
BasicCharacterData(
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class MountedVehiclesTest extends Specification {
|
|||
case vdata: VehicleData =>
|
||||
vdata.pos.coord mustEqual Vector3(4571.6875f, 5602.1875f, 93)
|
||||
vdata.pos.orient mustEqual Vector3(11.25f, 2.8125f, 92.8125f)
|
||||
vdata.pos.vel.contains(Vector3(31.71875f, 8.875f, -0.03125f)) mustEqual true
|
||||
vdata.pos.vel.contains(Vector3(114.1875f, 31.949999f, -0.1125f)) mustEqual true
|
||||
vdata.data.faction mustEqual PlanetSideEmpire.TR
|
||||
vdata.data.bops mustEqual false
|
||||
vdata.data.alternate mustEqual false
|
||||
|
|
@ -264,7 +264,7 @@ class MountedVehiclesTest extends Specification {
|
|||
PlacementData(
|
||||
Vector3(4571.6875f, 5602.1875f, 93),
|
||||
Vector3(11.25f, 2.8125f, 92.8125f),
|
||||
Some(Vector3(31.71875f, 8.875f, -0.03125f))
|
||||
Some(Vector3(114.1875f, 31.949999f, -0.1125f))
|
||||
),
|
||||
CommonFieldData(PlanetSideEmpire.TR, false, false, false, None, false, Some(false), None, PlanetSideGUID(3776)),
|
||||
false,
|
||||
|
|
|
|||
|
|
@ -1061,7 +1061,7 @@ class DamageableWeaponTurretDestructionTest extends ActorTest {
|
|||
assert(!turret.Destroyed)
|
||||
|
||||
turret.Actor ! Vitality.Damage(applyDamageToA) //also test destruction while jammered
|
||||
vehicleProbe.receiveN(2, 1000 milliseconds) //flush jammered messages (see above)
|
||||
vehicleProbe.receiveN(2, 1000 milliseconds) //flush jammered messages (see above)
|
||||
assert(turret.Health > turret.Definition.DamageDestroysAt)
|
||||
assert(turret.Jammed)
|
||||
assert(!turret.Destroyed)
|
||||
|
|
@ -1071,44 +1071,37 @@ class DamageableWeaponTurretDestructionTest extends ActorTest {
|
|||
player1Probe.expectNoMessage(500 milliseconds)
|
||||
val msg3 = player2Probe.receiveOne(200 milliseconds)
|
||||
val msg56 = vehicleProbe.receiveN(2, 200 milliseconds)
|
||||
assert(
|
||||
msg12_4.head match {
|
||||
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg12_4(1) match {
|
||||
case AvatarServiceMessage("test", AvatarAction.Destroy(PlanetSideGUID(2), _, _, Vector3(1, 0, 0))) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg3 match {
|
||||
case Player.Die(_) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg12_4(2) match {
|
||||
case AvatarServiceMessage("test", AvatarAction.ObjectDelete(PlanetSideGUID(0), PlanetSideGUID(5), _)) => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg56.head match {
|
||||
case VehicleServiceMessage.TurretUpgrade(SupportActor.ClearSpecific(List(t), _)) if turret eq t => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msg56(1) match {
|
||||
case VehicleServiceMessage.TurretUpgrade(TurretUpgrader.AddTask(t, _, TurretUpgrade.None, _))
|
||||
if t eq turret =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
msg12_4.head match {
|
||||
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => ;
|
||||
case _ =>
|
||||
assert(false, s"DamageableWeaponTurretDestructionTest-1: ${msg12_4.head}")
|
||||
}
|
||||
msg12_4(1) match {
|
||||
case AvatarServiceMessage("test", AvatarAction.Destroy(PlanetSideGUID(2), _, _, Vector3(1, 0, 0))) => ;
|
||||
case _ =>
|
||||
assert(false, s"DamageableWeaponTurretDestructionTest-2: ${msg12_4(1)}")
|
||||
}
|
||||
msg3 match {
|
||||
case Player.Die(_) => true
|
||||
case _ =>
|
||||
assert(false, s"DamageableWeaponTurretDestructionTest-3: player not dead - $msg3")
|
||||
}
|
||||
msg12_4(2) match {
|
||||
case AvatarServiceMessage("test", AvatarAction.ObjectDelete(PlanetSideGUID(0), PlanetSideGUID(5), _)) => ;
|
||||
case _ =>
|
||||
assert(false, s"DamageableWeaponTurretDestructionTest-4: ${msg12_4(2)}")
|
||||
}
|
||||
msg56.head match {
|
||||
case VehicleServiceMessage.TurretUpgrade(SupportActor.ClearSpecific(List(t), _)) if turret eq t => ;
|
||||
case _ =>
|
||||
assert(false, s"DamageableWeaponTurretDestructionTest-5: ${msg56.head}")
|
||||
}
|
||||
msg56(1) match {
|
||||
case VehicleServiceMessage.TurretUpgrade(TurretUpgrader.AddTask(t, _, TurretUpgrade.None, _)) if t eq turret => ;
|
||||
true
|
||||
case _ =>
|
||||
assert(false, s"DamageableWeaponTurretDestructionTest-6: ${msg56(1)}")
|
||||
}
|
||||
assert(turret.Health <= turret.Definition.DamageDestroysAt)
|
||||
assert(!turret.Jammed)
|
||||
assert(turret.Destroyed)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import net.psforever.objects.guid.source.MaxNumberSource
|
|||
import net.psforever.objects.serverobject.environment._
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.objects.vehicles.VehicleLockState
|
||||
import net.psforever.objects.vehicles.control.VehicleControl
|
||||
import net.psforever.objects.vehicles.control.{CargoCarrierControl, VehicleControl}
|
||||
import net.psforever.objects.vital.{VehicleShieldCharge, Vitality}
|
||||
import net.psforever.objects.zones.{Zone, ZoneMap}
|
||||
import net.psforever.packet.game._
|
||||
|
|
@ -66,16 +66,11 @@ class VehicleControlPrepareForDeletionPassengerTest extends ActorTest {
|
|||
vehicle.Actor ! Vehicle.Deconstruct()
|
||||
|
||||
val vehicle_msg = vehicleProbe.receiveN(1, 500 milliseconds)
|
||||
assert(
|
||||
vehicle_msg.head match {
|
||||
case VehicleServiceMessage(
|
||||
"test",
|
||||
VehicleAction.KickPassenger(PlanetSideGUID(2), 4, false, PlanetSideGUID(1))
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
vehicle_msg.head match {
|
||||
case VehicleServiceMessage("test", VehicleAction.KickPassenger(PlanetSideGUID(2), 4, true, PlanetSideGUID(1))) => ;
|
||||
case _ =>
|
||||
assert(false, s"VehicleControlPrepareForDeletionPassengerTest: ${vehicle_msg.head}")
|
||||
}
|
||||
assert(player1.VehicleSeated.isEmpty)
|
||||
assert(vehicle.Seats(1).occupant.isEmpty)
|
||||
}
|
||||
|
|
@ -96,19 +91,22 @@ class VehicleControlPrepareForDeletionMountedInTest extends FreedContextActorTes
|
|||
|
||||
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
|
||||
vehicle.Faction = PlanetSideEmpire.TR
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test-cargo")
|
||||
vehicle.Zone = zone
|
||||
val lodestar = Vehicle(GlobalDefinitions.lodestar)
|
||||
lodestar.Faction = PlanetSideEmpire.TR
|
||||
lodestar.Zone = zone
|
||||
val player1 = Player(VehicleTest.avatar1) //name="test1"
|
||||
val player2 = Player(VehicleTest.avatar2) //name="test2"
|
||||
|
||||
guid.register(vehicle, 1)
|
||||
guid.register(lodestar, 2)
|
||||
player1.GUID = PlanetSideGUID(3)
|
||||
var utilityId = 10
|
||||
guid.register(player1, 3)
|
||||
guid.register(player2, 4)
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test-cargo")
|
||||
lodestar.Actor = system.actorOf(Props(classOf[CargoCarrierControl], lodestar), "vehicle-test-carrier")
|
||||
var utilityId = 5
|
||||
lodestar.Utilities.values.foreach { util =>
|
||||
util().GUID = PlanetSideGUID(utilityId)
|
||||
guid.register(util(), utilityId)
|
||||
utilityId += 1
|
||||
}
|
||||
vehicle.Seats(1).mount(player1) //passenger mount
|
||||
|
|
@ -123,81 +121,49 @@ class VehicleControlPrepareForDeletionMountedInTest extends FreedContextActorTes
|
|||
zone.Transport ! Zone.Vehicle.Spawn(lodestar) //can not fake this
|
||||
|
||||
"VehicleControl" should {
|
||||
"if mounted as cargo, self-eject when marked for deconstruction" in {
|
||||
"self-eject when marked for deconstruction if mounted as cargo" in {
|
||||
assert(player1.VehicleSeated.nonEmpty)
|
||||
assert(vehicle.Seats(1).occupant.nonEmpty)
|
||||
assert(vehicle.MountedIn.nonEmpty)
|
||||
assert(lodestar.CargoHolds(1).isOccupied)
|
||||
vehicle.Actor ! Vehicle.Deconstruct()
|
||||
|
||||
val vehicle_msg = vehicleProbe.receiveN(6, 500 milliseconds)
|
||||
val vehicle_msg = vehicleProbe.receiveN(6, 1 minute)
|
||||
//dismounting as cargo messages
|
||||
assert(
|
||||
vehicle_msg.head match {
|
||||
case VehicleServiceMessage(
|
||||
_,
|
||||
VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 0, _))
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
vehicle_msg(1) match {
|
||||
case VehicleServiceMessage(
|
||||
_,
|
||||
VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 68, _))
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
vehicle_msg(2) match {
|
||||
case VehicleServiceMessage(
|
||||
"test",
|
||||
VehicleAction.SendResponse(
|
||||
_,
|
||||
CargoMountPointStatusMessage(PlanetSideGUID(2), _, PlanetSideGUID(1), _, 1, CargoStatus.InProgress, 0)
|
||||
)
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
vehicle_msg(3) match {
|
||||
case VehicleServiceMessage(
|
||||
"test",
|
||||
VehicleAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _, _, _))
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
vehicle_msg(4) match {
|
||||
case VehicleServiceMessage(
|
||||
"test",
|
||||
VehicleAction.SendResponse(
|
||||
_,
|
||||
CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0)
|
||||
)
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
//dismounting as cargo messages
|
||||
//TODO: does not actually kick out the cargo, but instigates the process
|
||||
assert(
|
||||
vehicle_msg(5) match {
|
||||
case VehicleServiceMessage(
|
||||
"test",
|
||||
VehicleAction.KickPassenger(PlanetSideGUID(3), 4, false, PlanetSideGUID(1))
|
||||
) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
vehicle_msg.head match {
|
||||
case VehicleServiceMessage("test", VehicleAction.KickPassenger(PlanetSideGUID(3), 4, true, PlanetSideGUID(1))) => ;
|
||||
case _ =>
|
||||
assert(false, s"VehicleControlPrepareForDeletionMountedInTest-1: ${vehicle_msg.head}")
|
||||
}
|
||||
vehicle_msg(1) match {
|
||||
case VehicleServiceMessage(_, VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 0, _))) => ;
|
||||
case _ =>
|
||||
assert(false, s"VehicleControlPrepareForDeletionMountedInTest-2: ${vehicle_msg(1)}")
|
||||
}
|
||||
vehicle_msg(2) match {
|
||||
case VehicleServiceMessage(_, VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 68, _))) => ;
|
||||
case _ =>
|
||||
assert(false, s"VehicleControlPrepareForDeletionMountedInTest-3: ${vehicle_msg(2)}")
|
||||
}
|
||||
vehicle_msg(3) match {
|
||||
case VehicleServiceMessage("test", VehicleAction.SendResponse(_, CargoMountPointStatusMessage(PlanetSideGUID(2), _, PlanetSideGUID(1), _, 1, CargoStatus.InProgress, 0))) => ;
|
||||
case _ =>
|
||||
assert(false, s"VehicleControlPrepareForDeletionMountedInTest-4: ${vehicle_msg(3)}")
|
||||
}
|
||||
vehicle_msg(4) match {
|
||||
case VehicleServiceMessage("test", VehicleAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _, _, _))) => ;
|
||||
case _ =>
|
||||
assert(false, s"VehicleControlPrepareForDeletionMountedInTest-5: ${vehicle_msg(4)}")
|
||||
}
|
||||
vehicle_msg(5) match {
|
||||
case VehicleServiceMessage("test", VehicleAction.SendResponse(_, CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0))) => ;
|
||||
case _ =>
|
||||
assert(false, s"VehicleControlPrepareForDeletionMountedInTest-6: ${vehicle_msg(5)}")
|
||||
}
|
||||
assert(player1.VehicleSeated.isEmpty)
|
||||
assert(vehicle.Seats(1).occupant.isEmpty)
|
||||
assert(vehicle.MountedIn.isEmpty)
|
||||
assert(!lodestar.CargoHolds(1).isOccupied)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -251,55 +217,37 @@ class VehicleControlPrepareForDeletionMountedCargoTest extends FreedContextActor
|
|||
|
||||
val vehicle_msg = vehicleProbe.receiveN(6, 500 milliseconds)
|
||||
vehicle_msg(5) match {
|
||||
case VehicleServiceMessage(
|
||||
"test",
|
||||
VehicleAction.KickPassenger(PlanetSideGUID(4), 4, false, PlanetSideGUID(2))
|
||||
) => ;
|
||||
case _ => assert(false)
|
||||
case VehicleServiceMessage("test", VehicleAction.KickPassenger(PlanetSideGUID(4), 4, true, PlanetSideGUID(2))) => ;
|
||||
case _ =>
|
||||
assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-1: ${vehicle_msg(5)}")
|
||||
}
|
||||
assert(player2.VehicleSeated.isEmpty)
|
||||
assert(lodestar.Seats(0).occupant.isEmpty)
|
||||
//cargo dismounting messages
|
||||
vehicle_msg.head match {
|
||||
case VehicleServiceMessage(
|
||||
_,
|
||||
VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 0, _))
|
||||
) => ;
|
||||
case _ => assert(false)
|
||||
case VehicleServiceMessage(_, VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 0, _))) => ;
|
||||
case _ =>
|
||||
assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-2: ${vehicle_msg.head}")
|
||||
}
|
||||
vehicle_msg(1) match {
|
||||
case VehicleServiceMessage(
|
||||
_,
|
||||
VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 68, _))
|
||||
) => ;
|
||||
case _ => assert(false)
|
||||
case VehicleServiceMessage(_, VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 68, _))) => ;
|
||||
case _ =>
|
||||
assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-3: ${vehicle_msg(1)}")
|
||||
}
|
||||
vehicle_msg(2) match {
|
||||
case VehicleServiceMessage(
|
||||
"test",
|
||||
VehicleAction.SendResponse(
|
||||
_,
|
||||
CargoMountPointStatusMessage(PlanetSideGUID(2), _, PlanetSideGUID(1), _, 1, CargoStatus.InProgress, 0)
|
||||
)
|
||||
) => ;
|
||||
case _ => assert(false)
|
||||
case VehicleServiceMessage("test", VehicleAction.SendResponse(_, CargoMountPointStatusMessage(PlanetSideGUID(2), _, PlanetSideGUID(1), _, 1, CargoStatus.InProgress, 0))) => ;
|
||||
case _ =>
|
||||
assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-4: ${vehicle_msg(2)}")
|
||||
}
|
||||
vehicle_msg(3) match {
|
||||
case VehicleServiceMessage(
|
||||
"test",
|
||||
VehicleAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _, _, _))
|
||||
) => ;
|
||||
case _ => assert(false)
|
||||
case VehicleServiceMessage("test", VehicleAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _, _, _))) => ;
|
||||
case _ =>
|
||||
assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-5: ${vehicle_msg(3)}")
|
||||
}
|
||||
vehicle_msg(4) match {
|
||||
case VehicleServiceMessage(
|
||||
"test",
|
||||
VehicleAction.SendResponse(
|
||||
_,
|
||||
CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0)
|
||||
)
|
||||
) => ;
|
||||
case _ => assert(false)
|
||||
case VehicleServiceMessage("test", VehicleAction.SendResponse(_, CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0))) => ;
|
||||
case _ =>
|
||||
assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-6: ${vehicle_msg(4)}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue