Cargo Bail (#258)

* deconstructing a vehicle that has cargo will eject the cargo before despawning the vehicle

* cargo that dismounts without a driver is scheduled for deconstruction; any player that disconnects while in a vehicle that is flying has the vehicle scheduled for immediate deconstruction instead of the standard deconstruction time

* modified routine for disowning vehicles outside of the conventional 'driver is owner' system

* standardized player-driven ownership fields; adjusted how vehicles transfer and announce ownership on zone join events (SetCurrentAvatar)

* stop /zone and /warp if in a cargo vehicle (even if driver)

* fix for landed-dismount cargo logic not starting distance checking loop

* concatenated Owner and OwnerName assignment to a singular object method, and the vehicle use of assignment to a singular WSA function
This commit is contained in:
Fate-JH 2019-05-06 09:10:39 -04:00 committed by GitHub
parent fca14e6b1b
commit a467a79c71
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 284 additions and 179 deletions

View file

@ -0,0 +1,65 @@
// Copyright (c) 2019 PSForever
package net.psforever.objects
import net.psforever.packet.game.PlanetSideGUID
trait OwnableByPlayer {
private var owner : Option[PlanetSideGUID] = None
private var ownerName : Option[String] = None
def Owner : Option[PlanetSideGUID] = owner
def Owner_=(owner : PlanetSideGUID) : Option[PlanetSideGUID] = Owner_=(Some(owner))
def Owner_=(owner : Player) : Option[PlanetSideGUID] = Owner_=(Some(owner.GUID))
def Owner_=(owner : Option[PlanetSideGUID]) : Option[PlanetSideGUID] = {
owner match {
case Some(_) =>
this.owner = owner
case None =>
this.owner = None
}
Owner
}
def OwnerName : Option[String] = ownerName
def OwnerName_=(owner : String) : Option[String] = OwnerName_=(Some(owner))
def OwnerName_=(owner : Player) : Option[String] = OwnerName_=(Some(owner.Name))
def OwnerName_=(owner : Option[String]) : Option[String] = {
owner match {
case Some(_) =>
ownerName = owner
case None =>
ownerName = None
}
OwnerName
}
/**
* na
* @param player na
* @return na
*/
def AssignOwnership(player : Player) : OwnableByPlayer = AssignOwnership(Some(player))
/**
* na
* @param playerOpt na
* @return na
*/
def AssignOwnership(playerOpt : Option[Player]) : OwnableByPlayer = {
playerOpt match {
case Some(player) =>
Owner = player
OwnerName = player
case None =>
Owner = None
OwnerName = None
}
this
}
}

View file

@ -72,10 +72,10 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
with MountedWeapons
with Deployment
with Vitality
with OwnableByPlayer
with StandardResistanceProfile
with Container {
private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.TR
private var owner : Option[PlanetSideGUID] = None
private var health : Int = 1
private var shields : Int = 0
private var decal : Int = 0
@ -139,26 +139,6 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
MountedIn
}
def Owner : Option[PlanetSideGUID] = {
this.owner
}
def Owner_=(owner : PlanetSideGUID) : Option[PlanetSideGUID] = Owner_=(Some(owner))
def Owner_=(owner : Player) : Option[PlanetSideGUID] = Owner_=(Some(owner.GUID))
def Owner_=(owner : Option[PlanetSideGUID]) : Option[PlanetSideGUID] = {
owner match {
case Some(_) =>
if(Definition.CanBeOwned) {
this.owner = owner
}
case None =>
this.owner = None
}
Owner
}
def Health : Int = {
health
}
@ -499,7 +479,7 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
if(trunkAccess.isEmpty || trunkAccess.contains(player.GUID)) {
groupPermissions(3) match {
case VehicleLockState.Locked => //only the owner
owner.isEmpty || (owner.isDefined && player.GUID == owner.get)
Owner.isEmpty || (Owner.isDefined && player.GUID == Owner.get)
case VehicleLockState.Group => //anyone in the owner's squad or platoon
faction == player.Faction //TODO this is not correct
case VehicleLockState.Empire => //anyone of the owner's faction

View file

@ -9,12 +9,11 @@ import net.psforever.packet.game.{DeployableIcon, PlanetSideGUID}
import net.psforever.types.PlanetSideEmpire
trait Deployable extends FactionAffinity
with Vitality {
with Vitality
with OwnableByPlayer {
this : PlanetSideGameObject =>
private var health : Int = 1
private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
private var owner : Option[PlanetSideGUID] = None
private var ownerName : Option[String] = None
def Health : Int = health
@ -32,38 +31,6 @@ trait Deployable extends FactionAffinity
Faction
}
def Owner : Option[PlanetSideGUID] = owner
def Owner_=(owner : PlanetSideGUID) : Option[PlanetSideGUID] = Owner_=(Some(owner))
def Owner_=(owner : Player) : Option[PlanetSideGUID] = Owner_=(Some(owner.GUID))
def Owner_=(owner : Option[PlanetSideGUID]) : Option[PlanetSideGUID] = {
owner match {
case Some(_) =>
this.owner = owner
case None =>
this.owner = None
}
Owner
}
def OwnerName : Option[String] = ownerName
def OwnerName_=(owner : String) : Option[String] = OwnerName_=(Some(owner))
def OwnerName_=(owner : Player) : Option[String] = OwnerName_=(Some(owner.Name))
def OwnerName_=(owner : Option[String]) : Option[String] = {
owner match {
case Some(_) =>
ownerName = owner
case None =>
ownerName = None
}
OwnerName
}
def DamageModel : DamageResistanceModel = Definition.asInstanceOf[DamageResistanceModel]
def Definition : ObjectDefinition with BaseDeployableDefinition

View file

@ -155,11 +155,11 @@ import scodec.codecs._
* `81 - ???`<br>
* `113 - Vehicle capacitor - e.g. Leviathan EMP charge`
*
* @param player_guid the player
* @param attribute_type na
* @param attribute_value na
* @param guid the object
* @param attribute_type the field
* @param attribute_value the value
*/
final case class PlanetsideAttributeMessage(player_guid : PlanetSideGUID,
final case class PlanetsideAttributeMessage(guid : PlanetSideGUID,
attribute_type : Int,
attribute_value : Long)
extends PlanetSideGamePacket {
@ -169,12 +169,16 @@ final case class PlanetsideAttributeMessage(player_guid : PlanetSideGUID,
}
object PlanetsideAttributeMessage extends Marshallable[PlanetsideAttributeMessage] {
def apply(player_guid : PlanetSideGUID, attribute_type : Int, attribute_value : Int) : PlanetsideAttributeMessage = {
PlanetsideAttributeMessage(player_guid, attribute_type, attribute_value.toLong)
def apply(guid : PlanetSideGUID, attribute_type : Int, attribute_value : Int) : PlanetsideAttributeMessage = {
PlanetsideAttributeMessage(guid, attribute_type, attribute_value.toLong)
}
def apply(guid : PlanetSideGUID, attribute_type : Int, attribute_value : PlanetSideGUID) : PlanetsideAttributeMessage = {
PlanetsideAttributeMessage(guid, attribute_type, attribute_value.guid)
}
implicit val codec : Codec[PlanetsideAttributeMessage] = (
("player_guid" | PlanetSideGUID.codec) ::
("guid" | PlanetSideGUID.codec) ::
("attribute_type" | uint8L) ::
("attribute_value" | uint32L)
).as[PlanetsideAttributeMessage]

View file

@ -36,5 +36,6 @@ object VehicleAction {
final case class TransferPassengerChannel(player_guid : PlanetSideGUID, temp_channel : String, new_channel : String, vehicle : Vehicle) extends Action
final case class TransferPassenger(player_guid : PlanetSideGUID, temp_channel : String, vehicle : Vehicle, vehicle_to_delete : PlanetSideGUID) extends Action
final case class KickCargo(player_guid : PlanetSideGUID, cargo : Vehicle, speed : Int) extends Action
final case class ForceDismountVehicleCargo(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID, bailed : Boolean, requestedByPassenger : Boolean, kicked : Boolean) extends Action
final case class KickCargo(player_guid : PlanetSideGUID, cargo : Vehicle, speed : Int, delay : Long) extends Action
}

View file

@ -40,5 +40,6 @@ object VehicleResponse {
final case class TransferPassengerChannel(old_channel : String, temp_channel : String, vehicle : Vehicle) extends Response
final case class TransferPassenger(temp_channel : String, vehicle : Vehicle, vehicle_to_delete : PlanetSideGUID) extends Response
final case class KickCargo(cargo : Vehicle, speed : Int) extends Response
final case class ForceDismountVehicleCargo(vehicle_guid : PlanetSideGUID, bailed : Boolean, requestedByPassenger : Boolean, kicked : Boolean) extends Response
final case class KickCargo(cargo : Vehicle, speed : Int, delay : Long) extends Response
}

View file

@ -140,9 +140,13 @@ class VehicleService extends Actor {
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.TransferPassenger(temp_channel, vehicle, vehicle_to_delete))
)
case VehicleAction.KickCargo(player_guid, cargo, speed) =>
case VehicleAction.ForceDismountVehicleCargo(player_guid, vehicle_guid, bailed, requestedByPassenger, kicked) =>
VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.KickCargo(cargo, speed))
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.ForceDismountVehicleCargo(vehicle_guid, bailed, requestedByPassenger, kicked))
)
case VehicleAction.KickCargo(player_guid, cargo, speed, delay) =>
VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.KickCargo(cargo, speed, delay))
)
case _ => ;
}

View file

@ -4,6 +4,7 @@ package services.vehicle.support
import net.psforever.objects.Vehicle
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.DriveState
import services.{RemoverActor, Service}
import services.vehicle.{VehicleAction, VehicleServiceMessage}
@ -26,6 +27,21 @@ class VehicleRemover extends RemoverActor {
val vehicleGUID = vehicle.GUID
val zoneId = entry.zone.Id
vehicle.Actor ! Vehicle.PrepareForDeletion
//escape being someone else's cargo
(vehicle.MountedIn match {
case Some(carrierGUID) =>
entry.zone.Vehicles.find(v => v.GUID == carrierGUID)
case None =>
None
}) match {
case Some(carrier : Vehicle) =>
val driverName = carrier.Seats(0).Occupant match {
case Some(driver) => driver.Name
case _ => zoneId
}
context.parent ! VehicleServiceMessage(s"$driverName", VehicleAction.ForceDismountVehicleCargo(PlanetSideGUID(0), vehicleGUID, true, false, false))
case _ => ;
}
//kick out all passengers
vehicle.Seats.values.foreach(seat => {
seat.Occupant match {
@ -37,6 +53,12 @@ class VehicleRemover extends RemoverActor {
}
case None => ;
}
//abandon all cargo
vehicle.CargoHolds.values
.collect { case hold if hold.isOccupied =>
val cargo = hold.Occupant.get
context.parent ! VehicleServiceMessage(zoneId, VehicleAction.ForceDismountVehicleCargo(PlanetSideGUID(0), cargo.GUID, true, false, false))
}
})
}

View file

@ -64,7 +64,6 @@ import services.local.support.{HackCaptureActor, RouterTelepadActivation}
import services.support.SupportActor
class WorldSessionActor extends Actor with MDCContextAware {
import WorldSessionActor._
private[this] val log = org.log4s.getLogger
@ -132,7 +131,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
*/
implicit def boolToInt(b : Boolean) : Int = if(b) 1 else 0
override def postStop() = {
override def postStop() : Unit = {
//TODO normally, player avatar persists a minute or so after disconnect; we are subject to the SessionReaper
clientKeepAlive.cancel
reviveTimer.cancel
@ -160,21 +159,22 @@ class WorldSessionActor extends Actor with MDCContextAware {
if(player.isAlive) {
//actually being alive or manually deconstructing
player.Position = Vector3.Zero
if(player.VehicleSeated.nonEmpty) {
//quickly and briefly kill player to avoid disembark animation
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player_guid, 0, 0))
DismountVehicleOnLogOut()
DisownVehicle()
//if seated, dismount
player.VehicleSeated match {
case Some(_) =>
//quickly and briefly kill player to avoid disembark animation?
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player_guid, 0, 0))
DismountVehicleOnLogOut()
case _ => ;
}
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, player_guid))
taskResolver ! GUIDTask.UnregisterAvatar(player)(continent.GUID)
//TODO normally, the actual player avatar persists a minute or so after the user disconnects
}
else if(continent.LivePlayers.contains(player) && !continent.Corpses.contains(player)) {
//player disconnected while waiting for a revive
//player disconnected while waiting for a revive, maybe
//similar to handling ReleaseAvatarRequestMessage
player.Release
DisownVehicle()
player.VehicleSeated match {
case None =>
FriskCorpse(player) //TODO eliminate dead letters
@ -191,7 +191,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
taskResolver ! GUIDTask.UnregisterAvatar(player)(continent.GUID)
}
case Some(vehicle_guid) =>
case Some(_) =>
val player_guid = player.GUID
player.Position = Vector3.Zero //save character before doing this
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, player_guid))
@ -199,7 +199,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
DismountVehicleOnLogOut()
}
}
DisownVehicle()
//disassociate and start the deconstruction timer for any currently owned vehicle
SpecialCaseDisownVehicle()
continent.Population ! Zone.Population.Leave(avatar)
}
}
@ -221,6 +222,29 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
}
/**
* If a vehicle is owned by a character, disassociate the vehicle, then schedule it for deconstruction.
* @see `DisownVehicle()`
* @return the vehicle previously owned, if any
*/
def SpecialCaseDisownVehicle() : Option[Vehicle] = {
DisownVehicle() match {
case out @ Some(vehicle) =>
vehicleService ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(vehicle), continent))
vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(vehicle, continent,
if(vehicle.Flying) {
Some(0 seconds) //immediate deconstruction
}
else {
vehicle.Definition.DeconstructionTime //normal deconstruction
}
))
out
case None =>
None
}
}
def receive = Initializing
def Initializing : Receive = {
@ -373,7 +397,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectHeld(player.GUID, player.LastDrawnSlot))
}
sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 1L)) //mount points off
sendResponse(PlanetsideAttributeMessage(vehicle_guid, 21, player.GUID.guid)) //ownership
sendResponse(PlanetsideAttributeMessage(player.GUID, 21, vehicle_guid)) //ownership
case VehicleSpawnPad.PlayerSeatedInVehicle(vehicle, pad) =>
val vehicle_guid = vehicle.GUID
@ -540,8 +564,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case Some(obj : BoomerDeployable) =>
val guid = obj.GUID
val factionOnContinentChannel = s"${continent.Id}/${player.Faction}"
obj.Owner = None
obj.OwnerName = None
obj.AssignOwnership(None)
avatar.Deployables.Remove(obj)
UpdateDeployableUIElements(avatar.Deployables.UpdateUIElement(obj.Definition.Item))
localService ! LocalServiceMessage.Deployables(RemoverActor.AddTask(obj, continent))
@ -580,8 +603,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
val playerGUID = player.GUID
val faction = player.Faction
val factionOnContinentChannel = s"${continent.Id}/${faction}"
obj.Owner = playerGUID
obj.OwnerName = player.Name
obj.AssignOwnership(player)
obj.Faction = faction
avatar.Deployables.Add(obj)
UpdateDeployableUIElements(avatar.Deployables.UpdateUIElement(obj.Definition.Item))
@ -635,8 +657,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
TryDropConstructionTool(tool, index, obj.Position)
sendResponse(ObjectDeployedMessage.Failure(obj.Definition.Name))
obj.Position = Vector3.Zero
obj.Owner = None
obj.OwnerName = None
obj.AssignOwnership(None)
continent.Deployables ! Zone.Deployable.Dismiss(obj)
}
@ -780,6 +801,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
localService ! Service.Leave(Some(continentId))
localService ! Service.Leave(Some(factionOnContinentChannel))
vehicleService ! Service.Leave(Some(continentId))
vehicleService ! Service.Leave(Some(factionOnContinentChannel))
player.Continent = zoneId
continent = zone
continent.Population ! Zone.Population.Join(avatar)
@ -1348,8 +1370,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
case None => ;
}
tplayer.VehicleOwned = Some(obj_guid)
obj.Owner = Some(tplayer.GUID)
OwnVehicle(obj, tplayer)
}
AccessContents(obj)
UpdateWeaponAtSeatPosition(obj, seat_num)
@ -1764,11 +1785,11 @@ class WorldSessionActor extends Actor with MDCContextAware {
avatar.Certifications += cert
StartBundlingPackets()
AddToDeployableQuantities(cert, player.Certifications)
sendResponse(PlanetsideAttributeMessage(guid, 24, cert.id.toLong))
sendResponse(PlanetsideAttributeMessage(guid, 24, cert.id))
tplayer.Certifications.intersect(Certification.Dependencies.Like(cert)).foreach(entry => {
log.info(s"$cert replaces the learned certification $entry that cost ${Certification.Cost.Of(entry)} points")
avatar.Certifications -= entry
sendResponse(PlanetsideAttributeMessage(guid, 25, entry.id.toLong))
sendResponse(PlanetsideAttributeMessage(guid, 25, entry.id))
})
StopBundlingPackets()
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Learn, true))
@ -1787,12 +1808,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
avatar.Certifications -= cert
StartBundlingPackets()
RemoveFromDeployablesQuantities(cert, player.Certifications)
sendResponse(PlanetsideAttributeMessage(guid, 25, cert.id.toLong))
sendResponse(PlanetsideAttributeMessage(guid, 25, cert.id))
tplayer.Certifications.intersect(Certification.Dependencies.FromAll(cert)).foreach(entry => {
log.info(s"$name is also forgetting the ${Certification.Cost.Of(entry)}-point $entry certification which depends on $cert")
avatar.Certifications -= entry
RemoveFromDeployablesQuantities(entry, player.Certifications)
sendResponse(PlanetsideAttributeMessage(guid, 25, entry.id.toLong))
sendResponse(PlanetsideAttributeMessage(guid, 25, entry.id))
})
StopBundlingPackets()
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Sell, true))
@ -2023,7 +2044,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
case VehicleResponse.Ownership(vehicle_guid) =>
sendResponse(PlanetsideAttributeMessage(guid, 21, vehicle_guid.guid))
sendResponse(PlanetsideAttributeMessage(tplayer_guid, 21, vehicle_guid))
case VehicleResponse.PlanetsideAttribute(vehicle_guid, attribute_type, attribute_value) =>
if(tplayer_guid != guid) {
@ -2098,7 +2119,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
}
case VehicleResponse.KickCargo(vehicle, speed) =>
case VehicleResponse.ForceDismountVehicleCargo(vehicle_guid, bailed, requestedByPassenger, kicked) =>
DismountVehicleCargo(tplayer_guid, vehicle_guid, bailed, requestedByPassenger, kicked)
case VehicleResponse.KickCargo(vehicle, speed, delay) =>
if(player.VehicleSeated.nonEmpty && deadState == DeadState.Alive) {
if(speed > 0) {
val strafe = if(CargoOrientation(vehicle) == 1) 2 else 1
@ -2107,7 +2130,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
controlled = Some(reverseSpeed)
sendResponse(ServerVehicleOverrideMsg(true, true, true, false, 0, strafe, reverseSpeed, Some(0)))
import scala.concurrent.ExecutionContext.Implicits.global
context.system.scheduler.scheduleOnce(4500 milliseconds, self, VehicleServiceResponse(toChannel, tplayer_guid, VehicleResponse.KickCargo(vehicle, 0)))
context.system.scheduler.scheduleOnce(delay milliseconds, self, VehicleServiceResponse(toChannel, tplayer_guid, VehicleResponse.KickCargo(vehicle, 0, delay)))
}
else {
controlled = None
@ -2678,12 +2701,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(SetCurrentAvatarMessage(guid, 0, 0))
sendResponse(ChatMsg(ChatMessageType.CMT_EXPANSIONS, true, "", "1 on", None)) //CC on //TODO once per respawn?
sendResponse(PlayerStateShiftMessage(ShiftState(1, tplayer.Position, tplayer.Orientation.z)))
//transfer vehicle ownership
player.VehicleOwned match {
case Some(vehicle_guid) if player.VehicleSeated.contains(vehicle_guid) =>
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.Ownership(guid, vehicle_guid))
case _ => ;
}
if(spectator) {
sendResponse(ChatMsg(ChatMessageType.CMT_TOGGLESPECTATORMODE, false, "", "on", None))
}
@ -2734,6 +2751,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
})
StopBundlingPackets()
drawDeloyableIcon = DontRedrawIcons
//assert or transfer vehicle ownership
continent.GUID(player.VehicleOwned) match {
case Some(vehicle : Vehicle) if vehicle.OwnerName.contains(tplayer.Name) =>
vehicle.Owner = guid
vehicleService ! VehicleServiceMessage(s"${continent.Id}/${tplayer.Faction}", VehicleAction.Ownership(guid, vehicle.GUID))
case _ =>
player.VehicleOwned = None
}
//if driver of a vehicle, summon any passengers and cargo vehicles left behind on previous continent
GetVehicleAndSeat() match {
case (Some(vehicle), Some(0)) =>
@ -2863,16 +2888,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ DismountVehicleCargoMsg(player_guid, vehicle_guid, bailed, requestedByPassenger, kicked) =>
log.info(msg.toString)
if(!requestedByPassenger) {
continent.GUID(vehicle_guid) match {
case Some(cargo : Vehicle) =>
continent.GUID(cargo.MountedIn) match {
case Some(ferry : Vehicle) =>
HandleDismountVehicleCargo(player_guid, vehicle_guid, cargo, ferry.GUID, ferry, bailed, requestedByPassenger, kicked)
case _ =>
log.warn(s"DismountVehicleCargoMsg: target ${cargo.Definition.Name} does not know what treats it as cargo")
}
case _ => ;
}
DismountVehicleCargo(player_guid, vehicle_guid, bailed, requestedByPassenger, kicked)
}
case msg @ CharacterCreateRequestMessage(name, head, voice, gender, empire) =>
@ -2908,6 +2924,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
localService ! Service.Join(continentId)
localService ! Service.Join(factionOnContinentChannel)
vehicleService ! Service.Join(continentId)
vehicleService ! Service.Join(factionOnContinentChannel)
galaxyService ! Service.Join("galaxy")
configZone(continent)
sendResponse(TimeOfDayMessage(1191182336))
@ -3342,7 +3359,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
deadState = DeadState.Release //cancel movement updates
PlayerActionsToCancel()
continent.GUID(player.VehicleSeated) match {
case Some(vehicle : Vehicle) =>
case Some(vehicle : Vehicle) if vehicle.MountedIn.isEmpty =>
vehicle.PassengerInSeat(player) match {
case Some(0) =>
vehicle.Position = pos
@ -3354,7 +3371,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
player.Position = pos
//avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player.GUID, player.GUID))
LoadZonePhysicalSpawnPoint(zone, pos, Vector3.Zero, 0)
case _ => //seated in something that is not a vehicle, or we're dead, in which case we can't move
case _ => //seated in something that is not a vehicle or the vehicle is cargo, in which case we can't move
deadState = DeadState.Alive
}
@ -3366,7 +3383,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
deadState = DeadState.Release //cancel movement updates
PlayerActionsToCancel()
continent.GUID(player.VehicleSeated) match {
case Some(vehicle : Vehicle) if player.isAlive =>
case Some(vehicle : Vehicle) if vehicle.MountedIn.isEmpty =>
vehicle.PassengerInSeat(player) match {
case Some(0) =>
vehicle.Position = pos
@ -3378,7 +3395,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
player.Position = pos
sendResponse(PlayerStateShiftMessage(ShiftState(0, pos, player.Orientation.z, None)))
deadState = DeadState.Alive //must be set here
case _ => //seated in something that is not a vehicle, or we're dead, in which case we can't move
case _ => //seated in something that is not a vehicle or the vehicle is cargo, in which case we can't move
deadState = DeadState.Alive
}
@ -4276,8 +4293,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
dObj.Position = pos
dObj.Orientation = orient
dObj.Faction = player.Faction
dObj.Owner = player.GUID
dObj.OwnerName = player.Name
dObj.AssignOwnership(player)
val tasking : TaskResolver.GiveTask = dObj match {
case turret : TurretDeployable =>
GUIDTask.RegisterDeployableTurret(turret)(continent.GUID)
@ -4617,7 +4633,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ DeployRequestMessage(player_guid, vehicle_guid, deploy_state, unk2, unk3, pos) =>
log.info(s"DeployRequest: $msg")
if(player.VehicleOwned == Some(vehicle_guid) && player.VehicleOwned == player.VehicleSeated) {
if(player.VehicleOwned.contains(vehicle_guid) && player.VehicleOwned == player.VehicleSeated) {
continent.GUID(vehicle_guid) match {
case Some(obj : Vehicle) =>
obj.Actor ! Deployment.TryDeploymentChange(deploy_state)
@ -5038,7 +5054,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
def Execute(resolver : ActorRef) : Unit = {
localDriver.VehicleSeated = localVehicle.GUID
localVehicle.Owner = localDriver.GUID
OwnVehicle(localVehicle, localDriver)
localAnnounce ! NewPlayerLoaded(localDriver) //alerts WSA
resolver ! scala.util.Success(this)
}
@ -5322,12 +5338,39 @@ class WorldSessionActor extends Actor with MDCContextAware {
* @param vehicle the `Vehicle`
*/
def ReloadVehicleAccessPermissions(vehicle : Vehicle) : Unit = {
val vehicle_guid = vehicle.GUID
(0 to 3).foreach(group => {
sendResponse(
PlanetsideAttributeMessage(vehicle_guid, group + 10, vehicle.PermissionGroup(group).get.id)
)
})
if(vehicle.Faction == player.Faction) {
val vehicle_guid = vehicle.GUID
(0 to 3).foreach(group => {
sendResponse(
PlanetsideAttributeMessage(vehicle_guid, group + 10, vehicle.PermissionGroup(group).get.id)
)
})
}
}
/**
* na
* @param vehicle na
* @param tplayer na
* @return na
*/
def OwnVehicle(vehicle : Vehicle, tplayer : Player) : Option[Vehicle] = OwnVehicle(vehicle, Some(tplayer))
/**
* na
* @param vehicle na
* @param playerOpt na
* @return na
*/
def OwnVehicle(vehicle : Vehicle, playerOpt : Option[Player]) : Option[Vehicle] = {
playerOpt match {
case Some(tplayer) =>
tplayer.VehicleOwned = vehicle.GUID
vehicle.AssignOwnership(playerOpt)
Some(vehicle)
case None =>
None
}
}
/**
@ -5340,7 +5383,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
* The vehicle must exist in the game world on the current continent.
* This is similar but unrelated to the natural exchange of ownership when someone else sits in the vehicle's driver seat.
* This is the player side of vehicle ownership removal.
* @see `SpecialCaseVehicleDespawn(Player, Vehicle)`
* @param tplayer the player
*/
def DisownVehicle(tplayer : Player) : Option[Vehicle] = {
@ -5349,7 +5391,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
tplayer.VehicleOwned = None
continent.GUID(vehicle_guid) match {
case Some(vehicle : Vehicle) =>
SpecialCaseVehicleDespawn(tplayer, vehicle)
DisownVehicle(tplayer, vehicle)
case _ =>
None
}
@ -5359,35 +5401,25 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
/**
* Disassociate a vehicle from the player that owns it, if that player really was the previous owner.
* Disassociate a player from a vehicle that he owns without associating a different player as the owner.
* Set the vehicle's driver seat permissions and passenger and gunner seat permissions to "allow empire,"
* then reload them for all clients.
* This is the vehicle side of vehicle ownership removal.
* Additionally, start the vehicle deconstruction timer if conditions are valid.
* @see `DisownVehicle(Player)`
* @param tplayer the player
* @param vehicle the discovered vehicle
*/
private def SpecialCaseVehicleDespawn(tplayer : Player, vehicle : Vehicle) : Option[Vehicle] = {
if(vehicle.Owner.contains(tplayer.GUID)) {
vehicle.Owner = None
vehicleService ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(vehicle), continent))
vehicle.CargoHolds.values
.collect { case hold if hold.isOccupied => SpecialCaseVehicleDespawn(player, hold.Occupant.get) }
continent.GUID(vehicle.MountedIn) match {
case Some(ferry : Vehicle) =>
HandleDismountVehicleCargo(
PlanetSideGUID(0),
vehicle.GUID,
vehicle,
ferry.GUID,
ferry,
false,
false,
false
)
vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(vehicle, continent, Some(0 seconds))) //instant vehicle decay
case _ =>
vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(vehicle, continent, vehicle.Definition.DeconstructionTime)) //normal vehicle decay
}
private def DisownVehicle(tplayer : Player, vehicle : Vehicle) : Option[Vehicle] = {
val pguid = tplayer.GUID
if(vehicle.Owner.contains(pguid)) {
vehicle.AssignOwnership(None)
val factionOnContinent = s"${continent.Id}/${vehicle.Faction}"
vehicleService ! VehicleServiceMessage(factionOnContinent, VehicleAction.Ownership(pguid, PlanetSideGUID(0)))
val vguid = vehicle.GUID
val empire = VehicleLockState.Empire.id
(0 to 2).foreach(group => {
vehicle.PermissionGroup(group, empire)
vehicleService ! VehicleServiceMessage(factionOnContinent, VehicleAction.SeatPermissions(pguid, vguid, group, empire))
})
ReloadVehicleAccessPermissions(vehicle)
Some(vehicle)
}
else {
@ -6629,8 +6661,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
player.VehicleSeated = guid
if(seat == 0) {
//if driver
player.VehicleOwned = guid
vehicle.Owner = player.GUID
OwnVehicle(vehicle, player)
vehicle.CargoHolds.values
.collect { case hold if hold.isOccupied => hold.Occupant.get }
.foreach { _.MountedIn = guid }
@ -7521,13 +7552,11 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
removed match {
case Some(telepad : TelepadDeployable) =>
telepad.Owner = None
telepad.OwnerName = None
telepad.AssignOwnership(None)
localService ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(telepad), continent))
localService ! LocalServiceMessage.Deployables(RemoverActor.AddTask(telepad, continent, Some(0 seconds))) //normal decay
case Some(old) =>
old.Owner = None
old.OwnerName = None
old.AssignOwnership(None)
localService ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(old), continent))
localService ! LocalServiceMessage.Deployables(RemoverActor.AddTask(old, continent, Some(0 seconds)))
if(msg) { //max test
@ -7959,7 +7988,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
* @param tplayer the target player being moved around;
* not necessarily the same player as the `WorldSessionActor`-global `player`
* @param zone_id the zone in which the player will be placed
* @return a tuple composed of an ActorRef` destination and a message to send to that destination
* @return a tuple composed of an `ActorRef` destination and a message to send to that destination
*/
def LoadZoneAsPlayer(tplayer : Player, zone_id : String) : (ActorRef, Any) = {
if(zone_id == continent.Id) {
@ -8058,12 +8087,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
hold.Occupant.get
}
occupiedCargoHolds.foreach{ cargo =>
cargo.Seat(0) match {
case Some(seat) if seat.isOccupied =>
vehicleService ! VehicleServiceMessage(s"${seat.Occupant.get.Name}", VehicleAction.TransferPassengerChannel(pguid, s"${cargo.Actor}", toChannel, cargo))
cargo.Seats(0).Occupant match {
case Some(occupant) =>
vehicleService ! VehicleServiceMessage(s"${occupant.Name}", VehicleAction.TransferPassengerChannel(pguid, s"${cargo.Actor}", toChannel, cargo))
case _ =>
log.error("LoadZoneInVehicleAsDriver: abort; vehicle in cargo hold missing driver")
SpecialCaseVehicleDespawn(player, cargo)
HandleDismountVehicleCargo(player.GUID, cargo.GUID, cargo, vehicle.GUID, vehicle, false, false, true)
}
}
//
@ -8486,6 +8515,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case Some((mountPoint, hold)) =>
cargo.MountedIn = None
hold.Occupant = None
val driverOpt = cargo.Seats(0).Occupant
val rotation : Vector3 = if(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)
@ -8511,11 +8541,16 @@ class WorldSessionActor extends Actor with MDCContextAware {
val resetCargoMsg = CargoMountPointStatusMessage(carrierGUID, PlanetSideGUID(0), PlanetSideGUID(0), cargoGUID, mountPoint, CargoStatus.Empty, 0)
sendResponse(ejectCargoMsg) //dismount vehicle on UI and disable "shield" effect on lodestar
sendResponse(detachCargoMsg)
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.SendResponse(player_guid, ejectCargoMsg))
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.SendResponse(player_guid, detachCargoMsg))
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.SendResponse(PlanetSideGUID(0), resetCargoMsg)) //lazy
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.SendResponse(player_guid, ejectCargoMsg))
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.SendResponse(player_guid, detachCargoMsg))
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.SendResponse(PlanetSideGUID(0), resetCargoMsg)) //lazy
log.debug(ejectCargoMsg.toString)
log.debug(detachCargoMsg.toString)
if(driverOpt.isEmpty) {
//TODO cargo should drop like a rock like normal; until then, deconstruct it
vehicleService ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(cargo), continent))
vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(cargo, continent, Some(0 seconds)))
}
}
else {
//the carrier vehicle is not flying; just open the door and let the cargo vehicle back out; force it out if necessary
@ -8525,19 +8560,23 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(cargoDetachMessage)
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.SendResponse(player_guid, cargoStatusMessage))
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.SendResponse(player_guid, cargoDetachMessage))
if(kicked) {
cargo.Seat(0).get.Occupant match {
case Some(driver) =>
vehicleService ! VehicleServiceMessage(s"${driver.Name}", VehicleAction.KickCargo(player_guid, cargo, cargo.Definition.AutoPilotSpeed2))
case None =>
//driverless vehicle will get cleaned up
}
driverOpt match {
case Some(driver) =>
if(kicked) {
vehicleService ! VehicleServiceMessage(s"${driver.Name}", VehicleAction.KickCargo(player_guid, cargo, cargo.Definition.AutoPilotSpeed2, 4500))
}
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
//check every quarter second if the vehicle has moved far enough away to be considered dismounted
cargoDismountTimer.cancel
cargoDismountTimer = context.system.scheduler.scheduleOnce(250 milliseconds, self, CheckCargoDismount(cargoGUID, carrierGUID, mountPoint, iteration = 0))
case None =>
val resetCargoMsg = CargoMountPointStatusMessage(carrierGUID, PlanetSideGUID(0), PlanetSideGUID(0), cargoGUID, mountPoint, CargoStatus.Empty, 0)
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.SendResponse(PlanetSideGUID(0), resetCargoMsg)) //lazy
//TODO cargo should back out like normal; until then, deconstruct it
vehicleService ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(cargo), continent))
vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(cargo, continent, Some(0 seconds)))
}
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
//check every quarter second if the vehicle has moved far enough away to be considered dismounted
cargoDismountTimer.cancel
cargoDismountTimer = context.system.scheduler.scheduleOnce(250 milliseconds, self, CheckCargoDismount(cargoGUID, carrierGUID, mountPoint, iteration = 0))
}
StopBundlingPackets()
@ -8546,6 +8585,28 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
}
/**
* na
* @param player_guid na
* @param vehicle_guid na
* @param bailed na
* @param requestedByPassenger na
* @param kicked na
*/
def DismountVehicleCargo(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID, bailed : Boolean, requestedByPassenger : Boolean, kicked : Boolean) : Unit = {
continent.GUID(vehicle_guid) match {
case Some(cargo : Vehicle) =>
continent.GUID(cargo.MountedIn) match {
case Some(ferry : Vehicle) =>
HandleDismountVehicleCargo(player_guid, vehicle_guid, cargo, ferry.GUID, ferry, bailed, requestedByPassenger, kicked)
case _ =>
log.warn(s"DismountVehicleCargo: target ${cargo.Definition.Name}@$vehicle_guid does not know what treats it as cargo")
}
case _ =>
log.warn(s"DismountVehicleCargo: target $vehicle_guid either is not a vehicle in ${continent.Id} or does not exist")
}
}
def GetPlayerHackSpeed(obj: PlanetSideServerObject with Hackable): Float = {
val playerHackLevel = GetPlayerHackLevel()
val timeToHack = obj.HackDuration(playerHackLevel)